ttrpc-0.8.1/.cargo_vcs_info.json0000644000000001360000000000100121760ustar { "git": { "sha1": "f669c050b63cfaf3ebf57390bc8f47d684ab9f4a" }, "path_in_vcs": "" }ttrpc-0.8.1/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000005441046102023000172060ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- # Description of problem (replace this text with the list of steps you followed) # Expected result (replace this text with an explanation of what you thought would happen) # Actual result (replace this text with details of what actually happened) ttrpc-0.8.1/.github/ISSUE_TEMPLATE/enhancement-request.md000064400000000000000000000010221046102023000210010ustar 00000000000000--- name: Enhancement request about: Suggest an improvement to an existing feature title: '' labels: '' assignees: '' --- **Which feature do you think can be improved?** Specify the feature you think could be made better. **How can it be improved?** Describe how specifically you think it could be improved. **Additional Information** Anything else to add? **Before raising this feature request** Have you looked at the [limitations document](https://github.com/kata-containers/documentation/blob/master/Limitations.md)? ttrpc-0.8.1/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000013751046102023000202440ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. **Before raising this feature request** Have you looked at the [limitations document](https://github.com/kata-containers/documentation/blob/master/Limitations.md)? ttrpc-0.8.1/.github/codecov.yml000064400000000000000000000000441046102023000144710ustar 00000000000000github_checks: annotations: false ttrpc-0.8.1/.github/workflows/bvt.yml000064400000000000000000000022371046102023000157050ustar 00000000000000name: BVT on: [pull_request] jobs: check: name: Check runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v3 - name: Check run: | make deps make check make -C compiler check make -C ttrpc-codegen check make: name: Build runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v3 - name: Build run: | make deps make make -C compiler make -C ttrpc-codegen make -C example build-examples deny: runs-on: ubuntu-latest strategy: matrix: checks: - advisories - bans sources # Prevent sudden announcement of a new advisory from failing ci: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: command: check ${{ matrix.checks }} ttrpc-0.8.1/.github/workflows/cov.yml000064400000000000000000000011601046102023000156730ustar 00000000000000name: Coverage on: [pull_request] jobs: coverage: runs-on: ubuntu-latest env: CARGO_TERM_COLOR: always steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup toolchain install stable --component llvm-tools-preview - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage run: cargo llvm-cov --all-features --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: files: lcov.info fail_ci_if_error: true ttrpc-0.8.1/.github/workflows/sanity.yml000064400000000000000000000016351046102023000164220ustar 00000000000000name: Sanity Check on: pull_request: types: - opened - synchronize - reopened - edited - labeled - unlabeled jobs: commits_check_job: runs-on: ubuntu-latest name: Commits Check steps: - name: Get PR Commits id: 'get-pr-commits' uses: tim-actions/get-pr-commits@master with: token: ${{ secrets.GITHUB_TOKEN }} - name: Commit Body Check uses: tim-actions/commit-body-check@master with: commits: ${{ steps.get-pr-commits.outputs.commits }} - name: DCO Check uses: tim-actions/dco@master with: commits: ${{ steps.get-pr-commits.outputs.commits }} pr_wip_check: runs-on: ubuntu-latest name: WIP Check steps: - name: WIP Check uses: tim-actions/wip-check@master with: labels: '["do-not-merge", "wip", "rfc"]' keywords: '["WIP", "wip", "RFC", "rfc"]' ttrpc-0.8.1/.gitignore000064400000000000000000000001711046102023000127550ustar 00000000000000target Cargo.lock *.rs.bk *.rs.fmt .vscode .idea *.o example/protocols/**/*.rs !example/protocols/**/mod.rs src/ttrpc.rs ttrpc-0.8.1/Cargo.toml0000644000000040530000000000100101760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "ttrpc" version = "0.8.1" authors = ["The AntFin Kata Team "] description = "A Rust version of ttrpc." homepage = "https://github.com/containerd/ttrpc-rust" readme = "README.md" keywords = [ "ttrpc", "protobuf", "rpc", ] license = "Apache-2.0" repository = "https://github.com/containerd/ttrpc-rust" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.async-trait] version = "0.1.31" optional = true [dependencies.byteorder] version = "1.3.2" [dependencies.crossbeam] version = "0.8.0" [dependencies.futures] version = "0.3" optional = true [dependencies.libc] version = "0.2.59" features = ["extra_traits"] [dependencies.log] version = "0.4" [dependencies.nix] version = "0.26.2" [dependencies.protobuf] version = "3.1.0" [dependencies.thiserror] version = "1.0" [dependencies.tokio] version = "1" features = [ "rt", "sync", "io-util", "macros", "time", ] optional = true [dev-dependencies.assert_cmd] version = "2.0.7" [build-dependencies.protobuf-codegen] version = "3.1.0" [features] async = [ "async-trait", "tokio", "futures", "tokio-vsock", ] default = ["sync"] sync = [] [target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies.tokio-vsock] version = "0.4.0" optional = true [target."cfg(windows)".dependencies.windows-sys] version = "0.48" features = [ "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Pipes", "Win32_Security", "Win32_System_Threading", ] ttrpc-0.8.1/Cargo.toml.orig000064400000000000000000000025401046102023000136560ustar 00000000000000[package] name = "ttrpc" version = "0.8.1" authors = ["The AntFin Kata Team "] edition = "2018" license = "Apache-2.0" keywords = ["ttrpc", "protobuf", "rpc"] readme = "README.md" repository = "https://github.com/containerd/ttrpc-rust" homepage = "https://github.com/containerd/ttrpc-rust" description = "A Rust version of ttrpc." [dependencies] protobuf = { version = "3.1.0" } libc = { version = "0.2.59", features = [ "extra_traits" ] } nix = "0.26.2" log = "0.4" byteorder = "1.3.2" thiserror = "1.0" async-trait = { version = "0.1.31", optional = true } tokio = { version = "1", features = ["rt", "sync", "io-util", "macros", "time"], optional = true } futures = { version = "0.3", optional = true } crossbeam = "0.8.0" [target.'cfg(windows)'.dependencies] windows-sys = {version = "0.48", features = [ "Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Pipes", "Win32_Security", "Win32_System_Threading"]} [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] tokio-vsock = { version = "0.4.0", optional = true } [build-dependencies] protobuf-codegen = "3.1.0" [dev-dependencies] assert_cmd = "2.0.7" [features] default = ["sync"] async = ["async-trait", "tokio", "futures", "tokio-vsock"] sync = [] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] ttrpc-0.8.1/LICENSE000064400000000000000000000261361046102023000120030ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ttrpc-0.8.1/MAINTAINERS000064400000000000000000000004431046102023000124640ustar 00000000000000# ttrpc-rust project maintainers # # COMMITTERS # GitHub ID, Name, Email address teawater, Hui Zhu, teawater@antfin.com lifupan, Fupan Li, fupan.lfp@alibaba-inc.com Tim-Zhang, Tim Zhang, tim@hyper.sh # REVIEWERS # GitHub ID, Name, Email address wllenyj, Lei Wang, wllenyj@linux.alibaba.com ttrpc-0.8.1/Makefile000064400000000000000000000011451046102023000124270ustar 00000000000000RUST_VERSION = 1.66 all: debug test # # Build # .PHONY: debug debug: cargo build --verbose --all-targets .PHONY: release release: cargo build --release .PHONY: build build: debug # # Tests and linters # .PHONY: test test: ifeq ($OS,Windows_NT) # async isn't enabled for windows, don't test that feature cargo test --verbose else cargo test --all-features --verbose endif .PHONY: check check: cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings .PHONY: deps deps: rustup install $(RUST_VERSION) rustup default $(RUST_VERSION) rustup component add rustfmt clippy ttrpc-0.8.1/README.md000064400000000000000000000070351046102023000122520ustar 00000000000000# ttrpc-rust _ttrpc-rust is a **non-core** subproject of containerd_ `ttrpc-rust` is the Rust version of [ttrpc](https://github.com/containerd/ttrpc). [ttrpc](https://github.com/containerd/ttrpc) is GRPC for low-memory environments. The ttrpc compiler of `ttrpc-rust` `ttrpc_rust_plugin` is modified from gRPC compiler of [gRPC-rs](https://github.com/pingcap/grpc-rs) [grpcio-compiler](https://github.com/pingcap/grpc-rs/tree/master/compiler). ## Usage ### 1. Generate with `protoc` command To generate the sources from proto files: 1. Install protoc from github.com/protocolbuffers/protobuf 2. Install protobuf-codegen ``` cargo install --force protobuf-codegen ``` 3. Install ttrpc_rust_plugin from ttrpc-rust/compiler ``` cd ttrpc-rust/compiler cargo install --force --path . ``` 4. Generate the sources: ``` $ protoc --rust_out=. --ttrpc_out=. --plugin=protoc-gen-ttrpc=`which ttrpc_rust_plugin` example.proto ``` ### 2. Generate programmatically API to generate .rs files to be used e. g. from build.rs. Example code: ``` fn main() { protoc_rust_ttrpc::Codegen::new() .out_dir("protocols") .inputs(&[ "protocols/protos/agent.proto", ]) .include("protocols/protos") .rust_protobuf() // also generate protobuf messages, not just services .run() .expect("Codegen failed."); } ``` # async/.await ttrpc-rust supports async/.await. By using async/.await you can reduce the overhead and resource consumption caused by threads. ## Usage ### 1. Generate codes in async version Currently we only support generating async codes by using ttrpc-codegen ``` ttrpc_codegen::Codegen::new() .out_dir("protocols/asynchronous") .inputs(&protos) .include("protocols/protos") .rust_protobuf() .customize(Customize { async_all: true, // It's the key option. ..Default::default() }) .run() .expect("Gen async codes failed."); ``` Provide customize option - `async_all`: generate async codes for both server and client - `async_server`: generate async codes for server - `async_client`: generate async codes for client > See more in `example/build.rs` ### 2. Write your implemention in async/.await's way Please follow the guidlines in `example/async-server.rs` and `example/async-client.rs` # Run Examples 1. Go to the directory ``` $ cd ttrpc-rust/example ``` 2. Start the server ``` $ cargo run --example server ``` or ``` $ cargo run --example async-server ``` 3. Start a client ``` $ cargo run --example client ``` or ``` $ cargo run --example async-client ``` # Notes: the version of protobuf protobuf-codegen, ttrpc_rust_plugin and your code should use the same version protobuf. You will get following fail if use the different version protobuf. ``` 27 | const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_8_0; | ^^^^^^^^^^^^^ help: a constant with a similar name exists: `VERSION_2_10_1` ``` The reason is that [files generated by protobuf-codegen are compatible only with the same version of runtime](https://github.com/stepancheg/rust-protobuf/commit/2ab4d50c27c4dd7803b64ce1a43e2c134532c7a6) To fix this issue: 1. Rebuild protobuf-codegen with new protobuf: ``` cd grpc-rs cargo clean cargo update cargo install --force protobuf-codegen ``` 2. Rebuild ttrpc_rust_plugin with new protobuf: ``` cd ttrpc-rust/compiler cargo clean cargo update cargo install --force --path . ``` 3. Build your project. ttrpc-0.8.1/build.rs000064400000000000000000000011141046102023000124300ustar 00000000000000use std::env; use std::fs; use std::path::PathBuf; fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let path: PathBuf = [out_dir.clone(), "mod.rs".to_string()].iter().collect(); fs::write(path, "pub mod ttrpc;").unwrap(); let customize = protobuf_codegen::Customize::default() .gen_mod_rs(false) .generate_accessors(true); protobuf_codegen::Codegen::new() .pure() .out_dir(out_dir) .inputs(["src/ttrpc.proto"]) .include("src") .customize(customize) .run() .expect("Codegen failed."); } ttrpc-0.8.1/deny.toml000064400000000000000000000213521046102023000126250ustar 00000000000000# This template contains all of the possible sections and their default values # Note that all fields that take a lint level have these possible values: # * deny - An error will be produced and the check will fail # * warn - A warning will be produced, but the check will not fail # * allow - No warning or error will be produced, though in some cases a note # will be # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration # If 1 or more target triples (and optionally, target_features) are specified, # only the specified targets will be checked when running `cargo deny check`. # This means, if a particular package is only ever used as a target specific # dependency, such as, for example, the `nix` crate only being used via the # `target_family = "unix"` configuration, that only having windows targets in # this list would mean the nix crate, as well as any of its exclusive # dependencies not shared by any other crates, would be ignored, as the target # list here is effectively saying which targets you are building for. targets = [ # The triple can be any string, but only the target triples built in to # rustc (as of 1.40) can be checked against actual config expressions #{ triple = "x86_64-unknown-linux-musl" }, # You can also specify which target_features you promise are enabled for a # particular target. target_features are currently not validated against # the actual valid features supported by the target architecture. #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, ] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for security vulnerabilities vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "warn" # The lint level for crates that have been yanked from their source registry yanked = "warn" # The lint level for crates with security notices. Note that as of # 2019-12-17 there are no security notice advisories in # https://github.com/rustsec/advisory-db notice = "warn" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [ #"RUSTSEC-0000-0000", ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories # will still output a note when they are encountered. # * None - CVSS Score 0.0 # * Low - CVSS Score 0.1 - 3.9 # * Medium - CVSS Score 4.0 - 6.9 # * High - CVSS Score 7.0 - 8.9 # * Critical - CVSS Score 9.0 - 10.0 #severity-threshold = # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" # List of explictly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "MIT", "Apache-2.0", "Unicode-DFS-2016", #"Apache-2.0 WITH LLVM-exception", ] # List of explictly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. deny = [ #"Nokia", ] # Lint level for licenses considered copyleft copyleft = "warn" # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses # * both - The license will be approved if it is both OSI-approved *AND* FSF # * either - The license will be approved if it is either OSI-approved *OR* FSF # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved # * neither - This predicate is ignored and the default lint level is used allow-osi-fsf-free = "neither" # Lint level used when no other predicates are matched # 1. License isn't in the allow or deny lists # 2. License isn't copyleft # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], name = "adler32", version = "*" }, ] # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information #[[licenses.clarify]] # The name of the crate the clarification applies to #name = "ring" # The optional version constraint for the crate #version = "*" # The SPDX expression for the license requirements of the crate #expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents #{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] # If true, ignores workspace crates that aren't published, or are only # published to private registries ignore = false # One or more private registries that you might publish crates to, if a crate # is only published to private registries, and ignore is true, the crate will # not have its license(s) checked registries = [ #"https://sekretz.com/registry ] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted # * simplest-path - The path to the version with the fewest edges is highlighted # * all - Both lowest-version and simplest-path are used highlight = "all" # List of crates that are allowed. Use with care! allow = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # List of crates to deny deny = [ # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. #{ name = "ansi_term", version = "=0.11.0" }, # # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ #{ name = "ansi_term", version = "=0.11.0" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive # dependencies starting at the specified crate, up to a certain depth, which is # by default infinite skip-tree = [ #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] # Lint level for what to happen when a crate from a crate registry that is not # in the allow list is encountered unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered unknown-git = "warn" # List of URLs for allowed crate registries. Defaults to the crates.io index # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] ttrpc-0.8.1/src/asynchronous/client.rs000064400000000000000000000242201046102023000161340ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use std::collections::HashMap; use std::convert::TryInto; use std::os::unix::io::RawFd; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Mutex}; use async_trait::async_trait; use nix::unistd::close; use tokio::{self, sync::mpsc, task}; use crate::common::client_connect; use crate::error::{get_rpc_status, Error, Result}; use crate::proto::{ Code, Codec, GenMessage, Message, MessageHeader, Request, Response, FLAG_NO_DATA, FLAG_REMOTE_CLOSED, FLAG_REMOTE_OPEN, MESSAGE_TYPE_DATA, MESSAGE_TYPE_RESPONSE, }; use crate::r#async::connection::*; use crate::r#async::shutdown; use crate::r#async::stream::{ Kind, MessageReceiver, MessageSender, ResultReceiver, ResultSender, StreamInner, }; use crate::r#async::utils; /// A ttrpc Client (async). #[derive(Clone)] pub struct Client { req_tx: MessageSender, next_stream_id: Arc, streams: Arc>>, } impl Client { pub fn connect(sockaddr: &str) -> Result { let fd = unsafe { client_connect(sockaddr)? }; Ok(Self::new(fd)) } /// Initialize a new [`Client`]. pub fn new(fd: RawFd) -> Client { let stream = utils::new_unix_stream_from_raw_fd(fd); let (req_tx, rx): (MessageSender, MessageReceiver) = mpsc::channel(100); let req_map = Arc::new(Mutex::new(HashMap::new())); let delegate = ClientBuilder { rx: Some(rx), streams: req_map.clone(), }; let conn = Connection::new(stream, delegate); tokio::spawn(async move { conn.run().await }); Client { req_tx, next_stream_id: Arc::new(AtomicU32::new(1)), streams: req_map, } } /// Requsts a unary request and returns with response. pub async fn request(&self, req: Request) -> Result { let timeout_nano = req.timeout_nano; let stream_id = self.next_stream_id.fetch_add(2, Ordering::Relaxed); let msg: GenMessage = Message::new_request(stream_id, req)? .try_into() .map_err(|e: protobuf::Error| Error::Others(e.to_string()))?; let (tx, mut rx): (ResultSender, ResultReceiver) = mpsc::channel(100); // TODO: check return. self.streams.lock().unwrap().insert(stream_id, tx); self.req_tx .send(msg) .await .map_err(|e| Error::Others(format!("Send packet to sender error {e:?}")))?; let result = if timeout_nano == 0 { rx.recv() .await .ok_or_else(|| Error::Others("Receive packet from receiver error".to_string()))? } else { tokio::time::timeout( std::time::Duration::from_nanos(timeout_nano as u64), rx.recv(), ) .await .map_err(|e| Error::Others(format!("Receive packet timeout {e:?}")))? .ok_or_else(|| Error::Others("Receive packet from receiver error".to_string()))? }; let msg = result?; let res = Response::decode(msg.payload) .map_err(err_to_others_err!(e, "Unpack response error "))?; let status = res.status(); if status.code() != Code::OK { return Err(Error::RpcStatus((*status).clone())); } Ok(res) } /// Creates a StreamInner instance. pub async fn new_stream( &self, req: Request, streaming_client: bool, streaming_server: bool, ) -> Result { let stream_id = self.next_stream_id.fetch_add(2, Ordering::Relaxed); let is_req_payload_empty = req.payload.is_empty(); let mut msg: GenMessage = Message::new_request(stream_id, req)? .try_into() .map_err(|e: protobuf::Error| Error::Others(e.to_string()))?; if streaming_client { if !is_req_payload_empty { return Err(get_rpc_status( Code::INVALID_ARGUMENT, "Creating a ClientStream and sending payload at the same time is not allowed", )); } msg.header.add_flags(FLAG_REMOTE_OPEN | FLAG_NO_DATA); } else { msg.header.add_flags(FLAG_REMOTE_CLOSED); } let (tx, rx): (ResultSender, ResultReceiver) = mpsc::channel(100); // TODO: check return self.streams.lock().unwrap().insert(stream_id, tx); self.req_tx .send(msg) .await .map_err(|e| Error::Others(format!("Send packet to sender error {e:?}")))?; Ok(StreamInner::new( stream_id, self.req_tx.clone(), rx, streaming_client, streaming_server, Kind::Client, self.streams.clone(), )) } } struct ClientClose { fd: RawFd, close_fd: RawFd, } impl Drop for ClientClose { fn drop(&mut self) { close(self.close_fd).unwrap(); close(self.fd).unwrap(); trace!("All client is droped"); } } #[derive(Debug)] struct ClientBuilder { rx: Option, streams: Arc>>, } impl Builder for ClientBuilder { type Reader = ClientReader; type Writer = ClientWriter; fn build(&mut self) -> (Self::Reader, Self::Writer) { let (notifier, waiter) = shutdown::new(); ( ClientReader { shutdown_waiter: waiter, streams: self.streams.clone(), }, ClientWriter { rx: self.rx.take().unwrap(), shutdown_notifier: notifier, streams: self.streams.clone(), }, ) } } struct ClientWriter { rx: MessageReceiver, shutdown_notifier: shutdown::Notifier, streams: Arc>>, } #[async_trait] impl WriterDelegate for ClientWriter { async fn recv(&mut self) -> Option { self.rx.recv().await } async fn disconnect(&self, msg: &GenMessage, e: Error) { // TODO: // At this point, a new request may have been received. let resp_tx = { let mut map = self.streams.lock().unwrap(); map.remove(&msg.header.stream_id) }; // TODO: if None if let Some(resp_tx) = resp_tx { let e = Error::Socket(format!("{e:?}")); resp_tx .send(Err(e)) .await .unwrap_or_else(|_e| error!("The request has returned")); } } async fn exit(&self) { self.shutdown_notifier.shutdown(); } } async fn get_resp_tx( req_map: Arc>>, header: &MessageHeader, ) -> Option { let resp_tx = match header.type_ { MESSAGE_TYPE_RESPONSE => match req_map.lock().unwrap().remove(&header.stream_id) { Some(tx) => tx, None => { debug!("Receiver got unknown response packet {:?}", header); return None; } }, MESSAGE_TYPE_DATA => { if (header.flags & FLAG_REMOTE_CLOSED) == FLAG_REMOTE_CLOSED { match req_map.lock().unwrap().remove(&header.stream_id) { Some(tx) => tx, None => { debug!("Receiver got unknown data packet {:?}", header); return None; } } } else { match req_map.lock().unwrap().get(&header.stream_id) { Some(tx) => tx.clone(), None => { debug!("Receiver got unknown data packet {:?}", header); return None; } } } } _ => { let resp_tx = match req_map.lock().unwrap().remove(&header.stream_id) { Some(tx) => tx, None => { debug!("Receiver got unknown packet {:?}", header); return None; } }; resp_tx .send(Err(Error::Others(format!( "Receiver got malformed packet {header:?}" )))) .await .unwrap_or_else(|_e| error!("The request has returned")); return None; } }; Some(resp_tx) } struct ClientReader { streams: Arc>>, shutdown_waiter: shutdown::Waiter, } #[async_trait] impl ReaderDelegate for ClientReader { async fn wait_shutdown(&self) { self.shutdown_waiter.wait_shutdown().await } async fn disconnect(&self, e: Error, sender: &mut task::JoinHandle<()>) { // Abort the request sender task to prevent incoming RPC requests // from being processed. sender.abort(); let _ = sender.await; // Take all items out of `req_map`. let mut map = std::mem::take(&mut *self.streams.lock().unwrap()); // Terminate undone RPC requests with the error. for (_stream_id, resp_tx) in map.drain() { if let Err(_e) = resp_tx.send(Err(e.clone())).await { warn!("Failed to terminate pending RPC: the request has returned"); } } } async fn exit(&self) {} async fn handle_err(&self, header: MessageHeader, e: Error) { let req_map = self.streams.clone(); tokio::spawn(async move { if let Some(resp_tx) = get_resp_tx(req_map, &header).await { resp_tx .send(Err(e)) .await .unwrap_or_else(|_e| error!("The request has returned")); } }); } async fn handle_msg(&self, msg: GenMessage) { let req_map = self.streams.clone(); tokio::spawn(async move { if let Some(resp_tx) = get_resp_tx(req_map, &msg.header).await { resp_tx .send(Ok(msg)) .await .unwrap_or_else(|_e| error!("The request has returned")); } }); } } ttrpc-0.8.1/src/asynchronous/connection.rs000064400000000000000000000065761046102023000170330ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use std::os::unix::io::AsRawFd; use async_trait::async_trait; use log::{error, trace}; use tokio::{ io::{split, AsyncRead, AsyncWrite, ReadHalf}, select, task, }; use crate::error::Error; use crate::proto::{GenMessage, GenMessageError, MessageHeader}; pub trait Builder { type Reader; type Writer; fn build(&mut self) -> (Self::Reader, Self::Writer); } #[async_trait] pub trait WriterDelegate { async fn recv(&mut self) -> Option; async fn disconnect(&self, msg: &GenMessage, e: Error); async fn exit(&self); } #[async_trait] pub trait ReaderDelegate { async fn wait_shutdown(&self); async fn disconnect(&self, e: Error, task: &mut task::JoinHandle<()>); async fn exit(&self); async fn handle_msg(&self, msg: GenMessage); async fn handle_err(&self, header: MessageHeader, e: Error); } pub struct Connection { reader: ReadHalf, writer_task: task::JoinHandle<()>, reader_delegate: B::Reader, } impl Connection where S: AsyncRead + AsyncWrite + AsRawFd + Send + 'static, B: Builder, B::Reader: ReaderDelegate + Send + Sync + 'static, B::Writer: WriterDelegate + Send + Sync + 'static, { pub fn new(conn: S, mut builder: B) -> Self { let (reader, mut writer) = split(conn); let (reader_delegate, mut writer_delegate) = builder.build(); let writer_task = tokio::spawn(async move { while let Some(msg) = writer_delegate.recv().await { trace!("write message: {:?}", msg); if let Err(e) = msg.write_to(&mut writer).await { error!("write_message got error: {:?}", e); writer_delegate.disconnect(&msg, e).await; } } writer_delegate.exit().await; trace!("Writer task exit."); }); Self { reader, writer_task, reader_delegate, } } pub async fn run(self) -> std::io::Result<()> { let Connection { mut reader, mut writer_task, reader_delegate, } = self; loop { select! { res = GenMessage::read_from(&mut reader) => { match res { Ok(msg) => { trace!("Got Message {:?}", msg); reader_delegate.handle_msg(msg).await; } Err(GenMessageError::ReturnError(header, e)) => { trace!("Read msg err (can be return): {:?}", e); reader_delegate.handle_err(header, e).await; } Err(GenMessageError::InternalError(e)) => { trace!("Read msg err: {:?}", e); reader_delegate.disconnect(e, &mut writer_task).await; break; } } } _v = reader_delegate.wait_shutdown() => { trace!("Receive shutdown."); break; } } } reader_delegate.exit().await; trace!("Reader task exit."); Ok(()) } } ttrpc-0.8.1/src/asynchronous/mod.rs000064400000000000000000000013021046102023000154310ustar 00000000000000// Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // //! Server and client in async mode (alias r#async). mod client; mod server; mod stream; #[macro_use] #[doc(hidden)] mod utils; mod connection; pub mod shutdown; mod unix_incoming; pub use self::stream::{ CSReceiver, CSSender, ClientStream, ClientStreamReceiver, ClientStreamSender, Kind, SSReceiver, SSSender, ServerStream, ServerStreamReceiver, ServerStreamSender, StreamInner, StreamReceiver, StreamSender, }; #[doc(inline)] pub use crate::r#async::client::Client; #[doc(inline)] pub use crate::r#async::server::{Server, Service}; #[doc(inline)] pub use utils::{MethodHandler, StreamHandler, TtrpcContext}; ttrpc-0.8.1/src/asynchronous/server.rs000064400000000000000000000524071046102023000161740ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use std::collections::HashMap; use std::convert::TryFrom; use std::marker::Unpin; use std::os::unix::io::RawFd; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::net::UnixListener as SysUnixListener; use std::result::Result as StdResult; use std::sync::{Arc, Mutex}; use std::time::Duration; use async_trait::async_trait; use futures::stream::Stream; use futures::StreamExt as _; use nix::unistd; use protobuf::Message as _; use tokio::{ self, io::{AsyncRead, AsyncWrite}, net::UnixListener, select, spawn, sync::mpsc::{channel, Sender}, task, time::timeout, }; #[cfg(any(target_os = "linux", target_os = "android"))] use tokio_vsock::VsockListener; use crate::asynchronous::unix_incoming::UnixIncoming; use crate::common::{self, Domain}; use crate::context; use crate::error::{get_status, Error, Result}; use crate::proto::{ check_oversize, Code, Codec, GenMessage, Message, MessageHeader, Request, Response, Status, FLAG_NO_DATA, FLAG_REMOTE_CLOSED, MESSAGE_TYPE_DATA, MESSAGE_TYPE_REQUEST, }; use crate::r#async::connection::*; use crate::r#async::shutdown; use crate::r#async::stream::{ Kind, MessageReceiver, MessageSender, ResultReceiver, ResultSender, StreamInner, }; use crate::r#async::utils; use crate::r#async::{MethodHandler, StreamHandler, TtrpcContext}; const DEFAULT_CONN_SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(5000); const DEFAULT_SERVER_SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(10000); pub struct Service { pub methods: HashMap>, pub streams: HashMap>, } impl Service { pub(crate) fn get_method(&self, name: &str) -> Option<&(dyn MethodHandler + Send + Sync)> { self.methods.get(name).map(|b| b.as_ref()) } pub(crate) fn get_stream(&self, name: &str) -> Option> { self.streams.get(name).cloned() } } /// A ttrpc Server (async). pub struct Server { listeners: Vec, services: Arc>, domain: Option, shutdown: shutdown::Notifier, stop_listen_tx: Option>>, } impl Default for Server { fn default() -> Self { Server { listeners: Vec::with_capacity(1), services: Arc::new(HashMap::new()), domain: None, shutdown: shutdown::with_timeout(DEFAULT_SERVER_SHUTDOWN_TIMEOUT).0, stop_listen_tx: None, } } } impl Server { pub fn new() -> Server { Server::default() } pub fn bind(mut self, sockaddr: &str) -> Result { if !self.listeners.is_empty() { return Err(Error::Others( "ttrpc-rust just support 1 sockaddr now".to_string(), )); } let (fd, domain) = common::do_bind(sockaddr)?; self.domain = Some(domain); common::do_listen(fd)?; self.listeners.push(fd); Ok(self) } pub fn set_domain_unix(mut self) -> Self { self.domain = Some(Domain::Unix); self } #[cfg(any(target_os = "linux", target_os = "android"))] pub fn set_domain_vsock(mut self) -> Self { self.domain = Some(Domain::Vsock); self } pub fn add_listener(mut self, fd: RawFd) -> Result { self.listeners.push(fd); Ok(self) } pub fn register_service(mut self, new: HashMap) -> Server { let services = Arc::get_mut(&mut self.services).unwrap(); services.extend(new); self } fn get_listenfd(&self) -> Result { if self.listeners.is_empty() { return Err(Error::Others("ttrpc-rust not bind".to_string())); } let listenfd = self.listeners[self.listeners.len() - 1]; Ok(listenfd) } pub async fn start(&mut self) -> Result<()> { let listenfd = self.get_listenfd()?; match self.domain.as_ref() { Some(Domain::Unix) => { let sys_unix_listener; unsafe { sys_unix_listener = SysUnixListener::from_raw_fd(listenfd); } sys_unix_listener .set_nonblocking(true) .map_err(err_to_others_err!(e, "set_nonblocking error "))?; let unix_listener = UnixListener::from_std(sys_unix_listener) .map_err(err_to_others_err!(e, "from_std error "))?; let incoming = UnixIncoming::new(unix_listener); self.do_start(incoming).await } // It seems that we can use UnixStream to represent both UnixStream and VsockStream. // Whatever, we keep it for now for the compatibility and vsock-specific features maybe // used in the future. #[cfg(any(target_os = "linux", target_os = "android"))] Some(Domain::Vsock) => { let incoming = unsafe { VsockListener::from_raw_fd(listenfd).incoming() }; self.do_start(incoming).await } _ => Err(Error::Others( "Domain is not set or not supported".to_string(), )), } } async fn do_start(&mut self, mut incoming: I) -> Result<()> where I: Stream> + Unpin + Send + 'static + AsRawFd, S: AsyncRead + AsyncWrite + AsRawFd + Send + 'static, { let services = self.services.clone(); let shutdown_waiter = self.shutdown.subscribe(); let (stop_listen_tx, mut stop_listen_rx) = channel(1); self.stop_listen_tx = Some(stop_listen_tx); spawn(async move { loop { select! { conn = incoming.next() => { if let Some(conn) = conn { // Accept a new connection match conn { Ok(conn) => { let fd = conn.as_raw_fd(); // spawn a connection handler, would not block spawn_connection_handler( fd, conn, services.clone(), shutdown_waiter.clone(), ).await; } Err(e) => { error!("{:?}", e) } } } else { break; } } fd_tx = stop_listen_rx.recv() => { if let Some(fd_tx) = fd_tx { // dup fd to keep the listener open // or the listener will be closed when the incoming was dropped. let dup_fd = unistd::dup(incoming.as_raw_fd()).unwrap(); common::set_fd_close_exec(dup_fd).unwrap(); drop(incoming); fd_tx.send(dup_fd).await.unwrap(); break; } } } } }); Ok(()) } pub async fn shutdown(&mut self) -> Result<()> { self.stop_listen().await; self.disconnect().await; while let Some(fd) = self.listeners.pop() { unistd::close(fd).unwrap_or_else(|e| { warn!("failed to close listener fd: {}", e); }); } Ok(()) } pub async fn disconnect(&mut self) { self.shutdown.shutdown(); self.shutdown .wait_all_exit() .await .map_err(|e| { trace!("wait connection exit error: {}", e); }) .ok(); trace!("wait connection exit."); } pub async fn stop_listen(&mut self) { if let Some(tx) = self.stop_listen_tx.take() { let (fd_tx, mut fd_rx) = channel(1); tx.send(fd_tx).await.unwrap(); let fd = fd_rx.recv().await.unwrap(); self.listeners.clear(); self.listeners.push(fd); } } } async fn spawn_connection_handler( fd: RawFd, conn: C, services: Arc>, shutdown_waiter: shutdown::Waiter, ) where C: AsyncRead + AsyncWrite + AsRawFd + Send + 'static, { let delegate = ServerBuilder { fd, services, streams: Arc::new(Mutex::new(HashMap::new())), shutdown_waiter, }; let conn = Connection::new(conn, delegate); spawn(async move { conn.run() .await .map_err(|e| { trace!("connection run error. {}", e); }) .ok(); }); } impl FromRawFd for Server { unsafe fn from_raw_fd(fd: RawFd) -> Self { Self::default().add_listener(fd).unwrap() } } impl AsRawFd for Server { fn as_raw_fd(&self) -> RawFd { self.listeners[0] } } struct ServerBuilder { fd: RawFd, services: Arc>, streams: Arc>>, shutdown_waiter: shutdown::Waiter, } impl Builder for ServerBuilder { type Reader = ServerReader; type Writer = ServerWriter; fn build(&mut self) -> (Self::Reader, Self::Writer) { let (tx, rx): (MessageSender, MessageReceiver) = channel(100); let (disconnect_notifier, _disconnect_waiter) = shutdown::with_timeout(DEFAULT_CONN_SHUTDOWN_TIMEOUT); ( ServerReader { fd: self.fd, tx, services: self.services.clone(), streams: self.streams.clone(), server_shutdown: self.shutdown_waiter.clone(), handler_shutdown: disconnect_notifier, }, ServerWriter { rx }, ) } } struct ServerWriter { rx: MessageReceiver, } #[async_trait] impl WriterDelegate for ServerWriter { async fn recv(&mut self) -> Option { self.rx.recv().await } async fn disconnect(&self, _msg: &GenMessage, _: Error) {} async fn exit(&self) {} } struct ServerReader { fd: RawFd, tx: MessageSender, services: Arc>, streams: Arc>>, server_shutdown: shutdown::Waiter, handler_shutdown: shutdown::Notifier, } #[async_trait] impl ReaderDelegate for ServerReader { async fn wait_shutdown(&self) { self.server_shutdown.wait_shutdown().await } async fn disconnect(&self, _: Error, _: &mut task::JoinHandle<()>) { self.handler_shutdown.shutdown(); // TODO: Don't wait for all requests to complete? when the connection is disconnected. } async fn exit(&self) { // TODO: Don't self.conn_shutdown.shutdown(); // Wait pedding request/stream to exit. self.handler_shutdown .wait_all_exit() .await .map_err(|e| { trace!("wait handler exit error: {}", e); }) .ok(); } async fn handle_msg(&self, msg: GenMessage) { let handler_shutdown_waiter = self.handler_shutdown.subscribe(); let context = self.context(); spawn(async move { select! { _ = context.handle_msg(msg) => {} _ = handler_shutdown_waiter.wait_shutdown() => {} } }); } async fn handle_err(&self, header: MessageHeader, e: Error) { self.context().handle_err(header, e).await } } impl ServerReader { fn context(&self) -> HandlerContext { HandlerContext { fd: self.fd, tx: self.tx.clone(), services: self.services.clone(), streams: self.streams.clone(), _handler_shutdown_waiter: self.handler_shutdown.subscribe(), } } } struct HandlerContext { fd: RawFd, tx: MessageSender, services: Arc>, streams: Arc>>, // Used for waiting handler exit. _handler_shutdown_waiter: shutdown::Waiter, } impl HandlerContext { async fn handle_err(&self, header: MessageHeader, e: Error) { Self::respond(self.tx.clone(), header.stream_id, e.into()) .await .map_err(|e| { error!("respond error got error {:?}", e); }) .ok(); } async fn handle_msg(&self, msg: GenMessage) { let stream_id = msg.header.stream_id; if (stream_id % 2) != 1 { Self::respond_with_status( self.tx.clone(), stream_id, get_status(Code::INVALID_ARGUMENT, "stream id must be odd"), ) .await; return; } match msg.header.type_ { MESSAGE_TYPE_REQUEST => match self.handle_request(msg).await { Ok(opt_msg) => match opt_msg { Some(mut resp) => { // Server: check size before sending to client if let Err(e) = check_oversize(resp.compute_size() as usize, true) { resp = e.into(); } Self::respond(self.tx.clone(), stream_id, resp) .await .map_err(|e| { error!("respond got error {:?}", e); }) .ok(); } None => { let mut header = MessageHeader::new_data(stream_id, 0); header.set_flags(FLAG_REMOTE_CLOSED | FLAG_NO_DATA); let msg = GenMessage { header, payload: Vec::new(), }; self.tx .send(msg) .await .map_err(err_to_others_err!(e, "Send packet to sender error ")) .ok(); } }, Err(status) => Self::respond_with_status(self.tx.clone(), stream_id, status).await, }, MESSAGE_TYPE_DATA => { // TODO(wllenyj): Compatible with golang behavior. if (msg.header.flags & FLAG_REMOTE_CLOSED) == FLAG_REMOTE_CLOSED && !msg.payload.is_empty() { Self::respond_with_status( self.tx.clone(), stream_id, get_status( Code::INVALID_ARGUMENT, format!( "Stream id {stream_id}: data close message connot include data" ), ), ) .await; return; } let stream_tx = self.streams.lock().unwrap().get(&stream_id).cloned(); if let Some(stream_tx) = stream_tx { if let Err(e) = stream_tx.send(Ok(msg)).await { Self::respond_with_status( self.tx.clone(), stream_id, get_status( Code::INVALID_ARGUMENT, format!("Stream id {stream_id}: handling data error: {e}"), ), ) .await; } } else { Self::respond_with_status( self.tx.clone(), stream_id, get_status(Code::INVALID_ARGUMENT, "Stream is no longer active"), ) .await; } } _ => { // TODO: else we must ignore this for future compat. log this? // TODO(wllenyj): Compatible with golang behavior. error!("Unknown message type. {:?}", msg.header); } } } async fn handle_request(&self, msg: GenMessage) -> StdResult, Status> { //TODO: //if header.stream_id <= self.last_stream_id { // return Err; //} // self.last_stream_id = header.stream_id; let req_msg = Message::::try_from(msg) .map_err(|e| get_status(Code::INVALID_ARGUMENT, e.to_string()))?; let req = &req_msg.payload; trace!("Got Message request {} {}", req.service, req.method); let srv = self.services.get(&req.service).ok_or_else(|| { get_status( Code::INVALID_ARGUMENT, format!("{} service does not exist", &req.service), ) })?; if let Some(method) = srv.get_method(&req.method) { return self.handle_method(method, req_msg).await; } if let Some(stream) = srv.get_stream(&req.method) { return self.handle_stream(stream, req_msg).await; } Err(get_status( Code::UNIMPLEMENTED, format!("{} method", &req.method), )) } async fn handle_method( &self, method: &(dyn MethodHandler + Send + Sync), req_msg: Message, ) -> StdResult, Status> { let req = req_msg.payload; let path = utils::get_path(&req.service, &req.method); let ctx = TtrpcContext { fd: self.fd, mh: req_msg.header, metadata: context::from_pb(&req.metadata), timeout_nano: req.timeout_nano, }; let get_unknown_status_and_log_err = |e| { error!("method handle {} got error {:?}", path, &e); get_status(Code::UNKNOWN, e) }; if req.timeout_nano == 0 { method .handler(ctx, req) .await .map_err(get_unknown_status_and_log_err) .map(Some) } else { timeout( Duration::from_nanos(req.timeout_nano as u64), method.handler(ctx, req), ) .await .map_err(|_| { // Timed out error!("method handle {} got error timed out", path); get_status(Code::DEADLINE_EXCEEDED, "timeout") }) .and_then(|r| { // Handler finished r.map_err(get_unknown_status_and_log_err) }) .map(Some) } } async fn handle_stream( &self, stream: Arc, req_msg: Message, ) -> StdResult, Status> { let stream_id = req_msg.header.stream_id; let req = req_msg.payload; let path = utils::get_path(&req.service, &req.method); let (tx, rx): (ResultSender, ResultReceiver) = channel(100); let stream_tx = tx.clone(); self.streams.lock().unwrap().insert(stream_id, tx); let no_data = (req_msg.header.flags & FLAG_NO_DATA) == FLAG_NO_DATA; let si = StreamInner::new( stream_id, self.tx.clone(), rx, true, // TODO true, Kind::Server, self.streams.clone(), ); let ctx = TtrpcContext { fd: self.fd, mh: req_msg.header, metadata: context::from_pb(&req.metadata), timeout_nano: req.timeout_nano, }; let task = spawn(async move { stream.handler(ctx, si).await }); if !no_data { // Fake the first data message. let msg = GenMessage { header: MessageHeader::new_data(stream_id, req.payload.len() as u32), payload: req.payload, }; stream_tx.send(Ok(msg)).await.map_err(|e| { error!("send stream data {} got error {:?}", path, &e); get_status(Code::UNKNOWN, e) })?; } task.await .unwrap_or_else(|e| Err(Error::Others(format!("stream {path} task got error {e:?}")))) .map_err(|e| get_status(Code::UNKNOWN, e)) } async fn respond(tx: MessageSender, stream_id: u32, resp: Response) -> Result<()> { let payload = resp .encode() .map_err(err_to_others_err!(e, "Encode Response failed."))?; let msg = GenMessage { header: MessageHeader::new_response(stream_id, payload.len() as u32), payload, }; tx.send(msg) .await .map_err(err_to_others_err!(e, "Send packet to sender error ")) } async fn respond_with_status(tx: MessageSender, stream_id: u32, status: Status) { let mut resp = Response::new(); resp.set_status(status); Self::respond(tx, stream_id, resp) .await .map_err(|e| { error!("respond with status got error {:?}", e); }) .ok(); } } ttrpc-0.8.1/src/asynchronous/shutdown.rs000064400000000000000000000201211046102023000165250ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 // use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use tokio::sync::Notify; use tokio::time::{error::Elapsed, timeout, Duration}; #[derive(Debug)] struct Shared { shutdown: AtomicBool, notify_shutdown: Notify, waiters: AtomicUsize, notify_exit: Notify, } impl Shared { fn is_shutdown(&self) -> bool { self.shutdown.load(Ordering::Relaxed) } } /// Wait for the shutdown notification. #[derive(Debug)] pub struct Waiter { shared: Arc, } /// Used to Notify all [`Waiter`s](Waiter) shutdown. /// /// No `Clone` is provided. If you want multiple instances, you can use Arc. /// Notifier will automatically call shutdown when dropping. #[derive(Debug)] pub struct Notifier { shared: Arc, wait_time: Option, } /// Create a new shutdown pair([`Notifier`], [`Waiter`]) without timeout. /// /// The [`Notifier`] pub fn new() -> (Notifier, Waiter) { _with_timeout(None) } /// Create a new shutdown pair with the specified [`Duration`]. /// /// The [`Duration`] is used to specify the timeout of the [`Notifier::wait_all_exit()`]. /// /// [`Duration`]: tokio::time::Duration pub fn with_timeout(wait_time: Duration) -> (Notifier, Waiter) { _with_timeout(Some(wait_time)) } fn _with_timeout(wait_time: Option) -> (Notifier, Waiter) { let shared = Arc::new(Shared { shutdown: AtomicBool::new(false), waiters: AtomicUsize::new(1), notify_shutdown: Notify::new(), notify_exit: Notify::new(), }); let notifier = Notifier { shared: shared.clone(), wait_time, }; let waiter = Waiter { shared }; (notifier, waiter) } impl Waiter { /// Return `true` if the [`Notifier::shutdown()`] has been called. /// /// [`Notifier::shutdown()`]: Notifier::shutdown() pub fn is_shutdown(&self) -> bool { self.shared.is_shutdown() } /// Waiting for the [`Notifier::shutdown()`] to be called. pub async fn wait_shutdown(&self) { while !self.is_shutdown() { let shutdown = self.shared.notify_shutdown.notified(); if self.is_shutdown() { return; } shutdown.await; } } fn from_shared(shared: Arc) -> Self { shared.waiters.fetch_add(1, Ordering::Relaxed); Self { shared } } } impl Clone for Waiter { fn clone(&self) -> Self { Self::from_shared(self.shared.clone()) } } impl Drop for Waiter { fn drop(&mut self) { if 1 == self.shared.waiters.fetch_sub(1, Ordering::Relaxed) { self.shared.notify_exit.notify_waiters(); } } } impl Notifier { /// Return `true` if the [`Notifier::shutdown()`] has been called. /// /// [`Notifier::shutdown()`]: Notifier::shutdown() pub fn is_shutdown(&self) -> bool { self.shared.is_shutdown() } /// Notify all [`Waiter`s](Waiter) shutdown. /// /// It will cause all calls blocking at `Waiter::wait_shutdown().await` to return. pub fn shutdown(&self) { let is_shutdown = self.shared.shutdown.swap(true, Ordering::Relaxed); if !is_shutdown { self.shared.notify_shutdown.notify_waiters(); } } /// Return the num of all [`Waiter`]s. pub fn waiters(&self) -> usize { self.shared.waiters.load(Ordering::Relaxed) } /// Create a new [`Waiter`]. pub fn subscribe(&self) -> Waiter { Waiter::from_shared(self.shared.clone()) } /// Wait for all [`Waiter`]s to drop. pub async fn wait_all_exit(&self) -> Result<(), Elapsed> { //debug_assert!(self.shared.is_shutdown()); if self.waiters() == 0 { return Ok(()); } let wait = self.wait(); if self.waiters() == 0 { return Ok(()); } wait.await } async fn wait(&self) -> Result<(), Elapsed> { if let Some(tm) = self.wait_time { timeout(tm, self.shared.notify_exit.notified()).await } else { self.shared.notify_exit.notified().await; Ok(()) } } } impl Drop for Notifier { fn drop(&mut self) { self.shutdown() } } #[cfg(test)] mod test { use super::*; #[tokio::test] async fn it_work() { let (notifier, waiter) = new(); let task = tokio::spawn(async move { waiter.wait_shutdown().await; }); assert_eq!(notifier.waiters(), 1); notifier.shutdown(); task.await.unwrap(); assert_eq!(notifier.waiters(), 0); } #[tokio::test] async fn notifier_drop() { let (notifier, waiter) = new(); assert_eq!(notifier.waiters(), 1); assert!(!waiter.is_shutdown()); drop(notifier); assert!(waiter.is_shutdown()); assert_eq!(waiter.shared.waiters.load(Ordering::Relaxed), 1); } #[tokio::test] async fn waiter_clone() { let (notifier, waiter1) = new(); assert_eq!(notifier.waiters(), 1); let waiter2 = waiter1.clone(); assert_eq!(notifier.waiters(), 2); let waiter3 = notifier.subscribe(); assert_eq!(notifier.waiters(), 3); drop(waiter2); assert_eq!(notifier.waiters(), 2); let task = tokio::spawn(async move { waiter3.wait_shutdown().await; assert!(waiter3.is_shutdown()); }); assert!(!waiter1.is_shutdown()); notifier.shutdown(); assert!(waiter1.is_shutdown()); task.await.unwrap(); assert_eq!(notifier.waiters(), 1); } #[tokio::test] async fn concurrency_notifier_shutdown() { let (notifier, waiter) = new(); let arc_notifier = Arc::new(notifier); let notifier1 = arc_notifier.clone(); let notifier2 = notifier1.clone(); let task1 = tokio::spawn(async move { assert_eq!(notifier1.waiters(), 1); let waiter = notifier1.subscribe(); assert_eq!(notifier1.waiters(), 2); notifier1.shutdown(); waiter.wait_shutdown().await; }); let task2 = tokio::spawn(async move { assert_eq!(notifier2.waiters(), 1); notifier2.shutdown(); }); waiter.wait_shutdown().await; assert!(arc_notifier.is_shutdown()); task1.await.unwrap(); task2.await.unwrap(); } #[tokio::test] async fn concurrency_notifier_wait() { let (notifier, waiter) = new(); let arc_notifier = Arc::new(notifier); let notifier1 = arc_notifier.clone(); let notifier2 = notifier1.clone(); let task1 = tokio::spawn(async move { notifier1.shutdown(); notifier1.wait_all_exit().await.unwrap(); }); let task2 = tokio::spawn(async move { notifier2.shutdown(); notifier2.wait_all_exit().await.unwrap(); }); waiter.wait_shutdown().await; drop(waiter); task1.await.unwrap(); task2.await.unwrap(); } #[tokio::test] async fn wait_all_exit() { let (notifier, waiter) = new(); let mut tasks = Vec::with_capacity(100); for i in 0..100 { assert_eq!(notifier.waiters(), 1 + i); let waiter1 = waiter.clone(); tasks.push(tokio::spawn(async move { waiter1.wait_shutdown().await; })); } drop(waiter); assert_eq!(notifier.waiters(), 100); notifier.shutdown(); notifier.wait_all_exit().await.unwrap(); for t in tasks { t.await.unwrap(); } } #[tokio::test] async fn wait_timeout() { let (notifier, waiter) = with_timeout(Duration::from_millis(100)); let task = tokio::spawn(async move { waiter.wait_shutdown().await; tokio::time::sleep(Duration::from_millis(200)).await; }); notifier.shutdown(); // Elapsed assert!(matches!(notifier.wait_all_exit().await, Err(_))); task.await.unwrap(); } } ttrpc-0.8.1/src/asynchronous/stream.rs000064400000000000000000000273151046102023000161610ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use std::collections::HashMap; use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use tokio::sync::mpsc; use crate::error::{Error, Result}; use crate::proto::{ Code, Codec, GenMessage, MessageHeader, Response, FLAG_NO_DATA, FLAG_REMOTE_CLOSED, MESSAGE_TYPE_DATA, MESSAGE_TYPE_RESPONSE, }; pub type MessageSender = mpsc::Sender; pub type MessageReceiver = mpsc::Receiver; pub type ResultSender = mpsc::Sender>; pub type ResultReceiver = mpsc::Receiver>; #[derive(Debug)] pub struct ClientStream { tx: CSSender, rx: CSReceiver

, } impl ClientStream where Q: Codec, P: Codec, ::E: std::fmt::Display,

::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { let (tx, rx) = inner.split(); Self { tx: CSSender { tx, _send: PhantomData, }, rx: CSReceiver { rx, _recv: PhantomData, }, } } pub fn split(self) -> (CSSender, CSReceiver

) { (self.tx, self.rx) } pub async fn send(&self, req: &Q) -> Result<()> { self.tx.send(req).await } pub async fn close_send(&self) -> Result<()> { self.tx.close_send().await } pub async fn recv(&mut self) -> Result

{ self.rx.recv().await } } #[derive(Clone, Debug)] pub struct CSSender { tx: StreamSender, _send: PhantomData, } impl CSSender where Q: Codec, ::E: std::fmt::Display, { pub async fn send(&self, req: &Q) -> Result<()> { let msg_buf = req .encode() .map_err(err_to_others_err!(e, "Encode message failed."))?; self.tx.send(msg_buf).await } pub async fn close_send(&self) -> Result<()> { self.tx.close_send().await } } #[derive(Debug)] pub struct CSReceiver

{ rx: StreamReceiver, _recv: PhantomData

, } impl

CSReceiver

where P: Codec,

::E: std::fmt::Display, { pub async fn recv(&mut self) -> Result

{ let msg_buf = self.rx.recv().await?; P::decode(msg_buf).map_err(err_to_others_err!(e, "Decode message failed.")) } } #[derive(Debug)] pub struct ServerStream { tx: SSSender

, rx: SSReceiver, } impl ServerStream where P: Codec, Q: Codec,

::E: std::fmt::Display, ::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { let (tx, rx) = inner.split(); Self { tx: SSSender { tx, _send: PhantomData, }, rx: SSReceiver { rx, _recv: PhantomData, }, } } pub fn split(self) -> (SSSender

, SSReceiver) { (self.tx, self.rx) } pub async fn send(&self, resp: &P) -> Result<()> { self.tx.send(resp).await } pub async fn recv(&mut self) -> Result> { self.rx.recv().await } } #[derive(Clone, Debug)] pub struct SSSender

{ tx: StreamSender, _send: PhantomData

, } impl

SSSender

where P: Codec,

::E: std::fmt::Display, { pub async fn send(&self, resp: &P) -> Result<()> { let msg_buf = resp .encode() .map_err(err_to_others_err!(e, "Encode message failed."))?; self.tx.send(msg_buf).await } } #[derive(Debug)] pub struct SSReceiver { rx: StreamReceiver, _recv: PhantomData, } impl SSReceiver where Q: Codec, ::E: std::fmt::Display, { pub async fn recv(&mut self) -> Result> { let res = self.rx.recv().await; if matches!(res, Err(Error::Eof)) { return Ok(None); } let msg_buf = res?; Q::decode(msg_buf) .map_err(err_to_others_err!(e, "Decode message failed.")) .map(Some) } } pub struct ClientStreamSender { inner: StreamInner, _send: PhantomData, _recv: PhantomData

, } impl ClientStreamSender where Q: Codec, P: Codec, ::E: std::fmt::Display,

::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { Self { inner, _send: PhantomData, _recv: PhantomData, } } pub async fn send(&self, req: &Q) -> Result<()> { let msg_buf = req .encode() .map_err(err_to_others_err!(e, "Encode message failed."))?; self.inner.send(msg_buf).await } pub async fn close_and_recv(&mut self) -> Result

{ self.inner.close_send().await?; let msg_buf = self.inner.recv().await?; P::decode(msg_buf).map_err(err_to_others_err!(e, "Decode message failed.")) } } pub struct ServerStreamSender

{ inner: StreamSender, _send: PhantomData

, } impl

ServerStreamSender

where P: Codec,

::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { Self { inner: inner.split().0, _send: PhantomData, } } pub async fn send(&self, resp: &P) -> Result<()> { let msg_buf = resp .encode() .map_err(err_to_others_err!(e, "Encode message failed."))?; self.inner.send(msg_buf).await } } pub struct ClientStreamReceiver

{ inner: StreamReceiver, _recv: PhantomData

, } impl

ClientStreamReceiver

where P: Codec,

::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { Self { inner: inner.split().1, _recv: PhantomData, } } pub async fn recv(&mut self) -> Result> { let res = self.inner.recv().await; if matches!(res, Err(Error::Eof)) { return Ok(None); } let msg_buf = res?; P::decode(msg_buf) .map_err(err_to_others_err!(e, "Decode message failed.")) .map(Some) } } pub struct ServerStreamReceiver { inner: StreamReceiver, _recv: PhantomData, } impl ServerStreamReceiver where Q: Codec, ::E: std::fmt::Display, { pub fn new(inner: StreamInner) -> Self { Self { inner: inner.split().1, _recv: PhantomData, } } pub async fn recv(&mut self) -> Result> { let res = self.inner.recv().await; if matches!(res, Err(Error::Eof)) { return Ok(None); } let msg_buf = res?; Q::decode(msg_buf) .map_err(err_to_others_err!(e, "Decode message failed.")) .map(Some) } } async fn _recv(rx: &mut ResultReceiver) -> Result { rx.recv().await.unwrap_or_else(|| { Err(Error::Others( "Receive packet from Receiver error".to_string(), )) }) } async fn _send(tx: &MessageSender, msg: GenMessage) -> Result<()> { tx.send(msg) .await .map_err(|e| Error::Others(format!("Send data packet to sender error {e:?}"))) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { Client, Server, } #[derive(Debug)] pub struct StreamInner { sender: StreamSender, receiver: StreamReceiver, } impl StreamInner { pub fn new( stream_id: u32, tx: MessageSender, rx: ResultReceiver, //waiter: shutdown::Waiter, sendable: bool, recveivable: bool, kind: Kind, streams: Arc>>, ) -> Self { Self { sender: StreamSender { tx, stream_id, sendable, local_closed: Arc::new(AtomicBool::new(false)), kind, }, receiver: StreamReceiver { rx, stream_id, recveivable, remote_closed: false, kind, streams, }, } } fn split(self) -> (StreamSender, StreamReceiver) { (self.sender, self.receiver) } pub async fn send(&self, buf: Vec) -> Result<()> { self.sender.send(buf).await } pub async fn close_send(&self) -> Result<()> { self.sender.close_send().await } pub async fn recv(&mut self) -> Result> { self.receiver.recv().await } } #[derive(Clone, Debug)] pub struct StreamSender { tx: MessageSender, stream_id: u32, sendable: bool, local_closed: Arc, kind: Kind, } #[derive(Debug)] pub struct StreamReceiver { rx: ResultReceiver, stream_id: u32, recveivable: bool, remote_closed: bool, kind: Kind, streams: Arc>>, } impl Drop for StreamReceiver { fn drop(&mut self) { self.streams.lock().unwrap().remove(&self.stream_id); } } impl StreamSender { pub async fn send(&self, buf: Vec) -> Result<()> { debug_assert!(self.sendable); if self.local_closed.load(Ordering::Relaxed) { debug_assert_eq!(self.kind, Kind::Client); return Err(Error::LocalClosed); } let header = MessageHeader::new_data(self.stream_id, buf.len() as u32); let msg = GenMessage { header, payload: buf, }; msg.check()?; _send(&self.tx, msg).await?; Ok(()) } pub async fn close_send(&self) -> Result<()> { debug_assert_eq!(self.kind, Kind::Client); debug_assert!(self.sendable); if self.local_closed.load(Ordering::Relaxed) { return Err(Error::LocalClosed); } let mut header = MessageHeader::new_data(self.stream_id, 0); header.set_flags(FLAG_REMOTE_CLOSED | FLAG_NO_DATA); let msg = GenMessage { header, payload: Vec::new(), }; _send(&self.tx, msg).await?; self.local_closed.store(true, Ordering::Relaxed); Ok(()) } } impl StreamReceiver { pub async fn recv(&mut self) -> Result> { if self.remote_closed { return Err(Error::RemoteClosed); } let msg = _recv(&mut self.rx).await?; let payload = match msg.header.type_ { MESSAGE_TYPE_RESPONSE => { debug_assert_eq!(self.kind, Kind::Client); self.remote_closed = true; let resp = Response::decode(&msg.payload) .map_err(err_to_others_err!(e, "Decode message failed."))?; if let Some(status) = resp.status.as_ref() { if status.code() != Code::OK { return Err(Error::RpcStatus((*status).clone())); } } resp.payload } MESSAGE_TYPE_DATA => { if !self.recveivable { self.remote_closed = true; return Err(Error::Others( "received data from non-streaming server.".to_string(), )); } if (msg.header.flags & FLAG_REMOTE_CLOSED) == FLAG_REMOTE_CLOSED { self.remote_closed = true; if (msg.header.flags & FLAG_NO_DATA) == FLAG_NO_DATA { return Err(Error::Eof); } } msg.payload } _ => { return Err(Error::Others("not support".to_string())); } }; Ok(payload) } } ttrpc-0.8.1/src/asynchronous/unix_incoming.rs000064400000000000000000000020061046102023000175220ustar 00000000000000// Copyright (c) 2021 Ant Group // // SPDX-License-Identifier: Apache-2.0 // //! Because Tokio has removed UnixIncoming since version 0.3, //! we define the UnixIncoming and implement the Stream for UnixIncoming. use std::io; use std::os::unix::io::{AsRawFd, RawFd}; use std::pin::Pin; use std::task::{Context, Poll}; use futures::{ready, Stream}; use tokio::net::{UnixListener, UnixStream}; /// Stream of listeners #[derive(Debug)] #[must_use = "streams do nothing unless polled"] pub struct UnixIncoming { inner: UnixListener, } impl UnixIncoming { pub fn new(listener: UnixListener) -> Self { Self { inner: listener } } } impl Stream for UnixIncoming { type Item = io::Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let (socket, _) = ready!(self.inner.poll_accept(cx))?; Poll::Ready(Some(Ok(socket))) } } impl AsRawFd for UnixIncoming { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } ttrpc-0.8.1/src/asynchronous/utils.rs000064400000000000000000000224331046102023000160220ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use std::collections::HashMap; use std::os::unix::io::{FromRawFd, RawFd}; use async_trait::async_trait; use tokio::net::UnixStream; use crate::error::Result; use crate::proto::{MessageHeader, Request, Response}; /// Handle request in async mode. #[macro_export] macro_rules! async_request_handler { ($class: ident, $ctx: ident, $req: ident, $server: ident, $req_type: ident, $req_fn: ident) => { let mut req = super::$server::$req_type::new(); { let mut s = CodedInputStream::from_bytes(&$req.payload); req.merge_from(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; } let mut res = ::ttrpc::Response::new(); match $class.service.$req_fn(&$ctx, req).await { Ok(rep) => { res.set_status(::ttrpc::get_status(::ttrpc::Code::OK, "".to_string())); res.payload.reserve(rep.compute_size() as usize); let mut s = protobuf::CodedOutputStream::vec(&mut res.payload); rep.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; } Err(x) => match x { ::ttrpc::Error::RpcStatus(s) => { res.set_status(s); } _ => { res.set_status(::ttrpc::get_status( ::ttrpc::Code::UNKNOWN, format!("{:?}", x), )); } }, } return Ok(res); }; } /// Handle client streaming in async mode. #[macro_export] macro_rules! async_client_streamimg_handler { ($class: ident, $ctx: ident, $inner: ident, $req_fn: ident) => { let stream = ::ttrpc::r#async::ServerStreamReceiver::new($inner); let mut res = ::ttrpc::Response::new(); match $class.service.$req_fn(&$ctx, stream).await { Ok(rep) => { res.set_status(::ttrpc::get_status(::ttrpc::Code::OK, "".to_string())); res.payload.reserve(rep.compute_size() as usize); let mut s = protobuf::CodedOutputStream::vec(&mut res.payload); rep.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; } Err(x) => match x { ::ttrpc::Error::RpcStatus(s) => { res.set_status(s); } _ => { res.set_status(::ttrpc::get_status( ::ttrpc::Code::UNKNOWN, format!("{:?}", x), )); } }, } return Ok(Some(res)); }; } /// Handle server streaming in async mode. #[macro_export] macro_rules! async_server_streamimg_handler { ($class: ident, $ctx: ident, $inner: ident, $server: ident, $req_type: ident, $req_fn: ident) => { let req_buf = $inner.recv().await?; let req = ::decode(&req_buf) .map_err(|e| ::ttrpc::Error::Others(e.to_string()))?; let stream = ::ttrpc::r#async::ServerStreamSender::new($inner); match $class.service.$req_fn(&$ctx, req, stream).await { Ok(_) => { return Ok(None); } Err(x) => { let mut res = ::ttrpc::Response::new(); match x { ::ttrpc::Error::RpcStatus(s) => { res.set_status(s); } _ => { res.set_status(::ttrpc::get_status( ::ttrpc::Code::UNKNOWN, format!("{:?}", x), )); } } return Ok(Some(res)); } } }; } /// Handle duplex streaming in async mode. #[macro_export] macro_rules! async_duplex_streamimg_handler { ($class: ident, $ctx: ident, $inner: ident, $req_fn: ident) => { let stream = ::ttrpc::r#async::ServerStream::new($inner); match $class.service.$req_fn(&$ctx, stream).await { Ok(_) => { return Ok(None); } Err(x) => { let mut res = ::ttrpc::Response::new(); match x { ::ttrpc::Error::RpcStatus(s) => { res.set_status(s); } _ => { res.set_status(::ttrpc::get_status( ::ttrpc::Code::UNKNOWN, format!("{:?}", x), )); } } return Ok(Some(res)); } } }; } /// Send request through async client. #[macro_export] macro_rules! async_client_request { ($self: ident, $ctx: ident, $req: ident, $server: expr, $method: expr, $cres: ident) => { let mut creq = ttrpc::Request { service: $server.to_string(), method: $method.to_string(), timeout_nano: $ctx.timeout_nano, metadata: ttrpc::context::to_pb($ctx.metadata), payload: Vec::with_capacity($req.compute_size() as usize), ..Default::default() }; { let mut s = CodedOutputStream::vec(&mut creq.payload); $req.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; } let res = $self.client.request(creq).await?; let mut s = CodedInputStream::from_bytes(&res.payload); $cres .merge_from(&mut s) .map_err(::ttrpc::err_to_others!(e, "Unpack get error "))?; return Ok($cres); }; } /// Duplex streaming through async client. #[macro_export] macro_rules! async_client_stream { ($self: ident, $ctx: ident, $server: expr, $method: expr) => { let mut creq = ::ttrpc::Request::new(); creq.set_service($server.to_string()); creq.set_method($method.to_string()); creq.set_timeout_nano($ctx.timeout_nano); let md = ::ttrpc::context::to_pb($ctx.metadata); creq.set_metadata(md); let inner = $self.client.new_stream(creq, true, true).await?; let stream = ::ttrpc::r#async::ClientStream::new(inner); return Ok(stream); }; } /// Only send streaming through async client. #[macro_export] macro_rules! async_client_stream_send { ($self: ident, $ctx: ident, $server: expr, $method: expr) => { let mut creq = ::ttrpc::Request::new(); creq.set_service($server.to_string()); creq.set_method($method.to_string()); creq.set_timeout_nano($ctx.timeout_nano); let md = ::ttrpc::context::to_pb($ctx.metadata); creq.set_metadata(md); let inner = $self.client.new_stream(creq, true, false).await?; let stream = ::ttrpc::r#async::ClientStreamSender::new(inner); return Ok(stream); }; } /// Only receive streaming through async client. #[macro_export] macro_rules! async_client_stream_receive { ($self: ident, $ctx: ident, $req: ident, $server: expr, $method: expr) => { let mut creq = ::ttrpc::Request::new(); creq.set_service($server.to_string()); creq.set_method($method.to_string()); creq.set_timeout_nano($ctx.timeout_nano); let md = ::ttrpc::context::to_pb($ctx.metadata); creq.set_metadata(md); creq.payload.reserve($req.compute_size() as usize); { let mut s = CodedOutputStream::vec(&mut creq.payload); $req.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; } let inner = $self.client.new_stream(creq, false, true).await?; let stream = ::ttrpc::r#async::ClientStreamReceiver::new(inner); return Ok(stream); }; } /// Trait that implements handler which is a proxy to the desired method (async). #[async_trait] pub trait MethodHandler { async fn handler(&self, ctx: TtrpcContext, req: Request) -> Result; } /// Trait that implements handler which is a proxy to the stream (async). #[async_trait] pub trait StreamHandler { async fn handler( &self, ctx: TtrpcContext, stream: crate::r#async::StreamInner, ) -> Result>; } /// The context of ttrpc (async). #[derive(Debug)] pub struct TtrpcContext { pub fd: std::os::unix::io::RawFd, pub mh: MessageHeader, pub metadata: HashMap>, pub timeout_nano: i64, } pub(crate) fn new_unix_stream_from_raw_fd(fd: RawFd) -> UnixStream { let std_stream: std::os::unix::net::UnixStream; unsafe { std_stream = std::os::unix::net::UnixStream::from_raw_fd(fd); } // Notice: There is a big change between tokio 1.0 and 0.2 // we must set nonblocking by ourselves in tokio 1.0 std_stream.set_nonblocking(true).unwrap(); UnixStream::from_std(std_stream).unwrap() } pub(crate) fn get_path(service: &str, method: &str) -> String { format!("/{service}/{method}") } ttrpc-0.8.1/src/common.rs000064400000000000000000000171461046102023000134240ustar 00000000000000#![cfg(not(windows))] // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // //! Common functions. #[cfg(any( feature = "async", not(any(target_os = "linux", target_os = "android")) ))] use nix::fcntl::FdFlag; use nix::fcntl::{fcntl, FcntlArg, OFlag}; use nix::sys::socket::*; use std::os::unix::io::RawFd; use crate::error::{Error, Result}; #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum Domain { Unix, #[cfg(any(target_os = "linux", target_os = "android"))] Vsock, } pub(crate) fn do_listen(listener: RawFd) -> Result<()> { if let Err(e) = fcntl(listener, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) { return Err(Error::Others(format!( "failed to set listener fd: {listener} as non block: {e}" ))); } listen(listener, 10).map_err(|e| Error::Socket(e.to_string())) } #[cfg(any(target_os = "linux", target_os = "android"))] fn parse_sockaddr(addr: &str) -> Result<(Domain, &str)> { if let Some(addr) = addr.strip_prefix("unix://") { return Ok((Domain::Unix, addr)); } if let Some(addr) = addr.strip_prefix("vsock://") { return Ok((Domain::Vsock, addr)); } Err(Error::Others(format!("Scheme {addr:?} is not supported"))) } #[cfg(not(any(target_os = "linux", target_os = "android")))] fn parse_sockaddr(addr: &str) -> Result<(Domain, &str)> { if let Some(addr) = addr.strip_prefix("unix://") { if addr.starts_with('@') { return Err(Error::Others( "Abstract unix domain socket is not support on this platform".to_string(), )); } return Ok((Domain::Unix, addr)); } Err(Error::Others(format!("Scheme {addr:?} is not supported"))) } #[cfg(any( feature = "async", not(any(target_os = "linux", target_os = "android")) ))] pub(crate) fn set_fd_close_exec(fd: RawFd) -> Result { if let Err(e) = fcntl(fd, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)) { return Err(Error::Others(format!( "failed to set fd: {fd} as close-on-exec: {e}" ))); } Ok(fd) } // SOCK_CLOEXEC flag is Linux specific #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) const SOCK_CLOEXEC: SockFlag = SockFlag::SOCK_CLOEXEC; #[cfg(not(any(target_os = "linux", target_os = "android")))] pub(crate) const SOCK_CLOEXEC: SockFlag = SockFlag::empty(); #[cfg(any(target_os = "linux", target_os = "android"))] fn make_addr(domain: Domain, sockaddr: &str) -> Result { match domain { Domain::Unix => { if let Some(sockaddr) = sockaddr.strip_prefix('@') { UnixAddr::new_abstract(sockaddr.as_bytes()).map_err(err_to_others_err!(e, "")) } else { UnixAddr::new(sockaddr).map_err(err_to_others_err!(e, "")) } } Domain::Vsock => Err(Error::Others( "function make_addr does not support create vsock socket".to_string(), )), } } #[cfg(not(any(target_os = "linux", target_os = "android")))] fn make_addr(_domain: Domain, sockaddr: &str) -> Result { UnixAddr::new(sockaddr).map_err(err_to_others_err!(e, "")) } fn make_socket(addr: (&str, u32)) -> Result<(RawFd, Domain, Box)> { let (sockaddr, _) = addr; let (domain, sockaddrv) = parse_sockaddr(sockaddr)?; let get_sock_addr = |domain, sockaddr| -> Result<(RawFd, Box)> { let fd = socket(AddressFamily::Unix, SockType::Stream, SOCK_CLOEXEC, None) .map_err(|e| Error::Socket(e.to_string()))?; // MacOS doesn't support atomic creation of a socket descriptor with SOCK_CLOEXEC flag, // so there is a chance of leak if fork + exec happens in between of these calls. #[cfg(target_os = "macos")] set_fd_close_exec(fd)?; let sockaddr = make_addr(domain, sockaddr)?; Ok((fd, Box::new(sockaddr))) }; let (fd, sockaddr): (i32, Box) = match domain { Domain::Unix => get_sock_addr(domain, sockaddrv)?, #[cfg(any(target_os = "linux", target_os = "android"))] Domain::Vsock => { let sockaddr_port_v: Vec<&str> = sockaddrv.split(':').collect(); if sockaddr_port_v.len() != 2 { return Err(Error::Others(format!( "sockaddr {sockaddr} is not right for vsock" ))); } let port: u32 = sockaddr_port_v[1] .parse() .expect("the vsock port is not an number"); let fd = socket( AddressFamily::Vsock, SockType::Stream, SockFlag::SOCK_CLOEXEC, None, ) .map_err(|e| Error::Socket(e.to_string()))?; let cid = addr.1; let sockaddr = VsockAddr::new(cid, port); (fd, Box::new(sockaddr)) } }; Ok((fd, domain, sockaddr)) } // Vsock is not supported on non Linux. #[cfg(any(target_os = "linux", target_os = "android"))] use libc::VMADDR_CID_ANY; #[cfg(not(any(target_os = "linux", target_os = "android")))] const VMADDR_CID_ANY: u32 = 0; #[cfg(any(target_os = "linux", target_os = "android"))] use libc::VMADDR_CID_HOST; #[cfg(not(any(target_os = "linux", target_os = "android")))] const VMADDR_CID_HOST: u32 = 0; pub(crate) fn do_bind(sockaddr: &str) -> Result<(RawFd, Domain)> { let (fd, domain, sockaddr) = make_socket((sockaddr, VMADDR_CID_ANY))?; setsockopt(fd, sockopt::ReusePort, &true)?; bind(fd, sockaddr.as_ref()).map_err(err_to_others_err!(e, ""))?; Ok((fd, domain)) } /// Creates a unix socket for client. pub(crate) unsafe fn client_connect(sockaddr: &str) -> Result { let (fd, _, sockaddr) = make_socket((sockaddr, VMADDR_CID_HOST))?; connect(fd, sockaddr.as_ref())?; Ok(fd) } #[cfg(test)] mod tests { use super::*; #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_parse_sockaddr() { for i in &[ ( "unix:///run/a.sock", Some(Domain::Unix), "/run/a.sock", true, ), ("vsock://8:1024", Some(Domain::Vsock), "8:1024", true), ("Vsock://8:1025", Some(Domain::Vsock), "8:1025", false), ( "unix://@/run/b.sock", Some(Domain::Unix), "@/run/b.sock", true, ), ("abc:///run/c.sock", None, "", false), ] { let (input, domain, addr, success) = (i.0, i.1, i.2, i.3); let r = parse_sockaddr(input); if success { let (rd, ra) = r.unwrap(); assert_eq!(rd, domain.unwrap()); assert_eq!(ra, addr); } else { assert!(r.is_err()); } } } #[cfg(not(any(target_os = "linux", target_os = "android")))] #[test] fn test_parse_sockaddr() { for i in &[ ( "unix:///run/a.sock", Some(Domain::Unix), "/run/a.sock", true, ), ("vsock:///run/c.sock", None, "", false), ("Vsock:///run/c.sock", None, "", false), ("unix://@/run/b.sock", None, "", false), ("abc:///run/c.sock", None, "", false), ] { let (input, domain, addr, success) = (i.0, i.1, i.2, i.3); let r = parse_sockaddr(input); if success { let (rd, ra) = r.unwrap(); assert_eq!(rd, domain.unwrap()); assert_eq!(ra, addr); } else { assert!(r.is_err()); } } } } ttrpc-0.8.1/src/context.rs000064400000000000000000000104701046102023000136110ustar 00000000000000// Copyright (c) 2021 Ant group // // SPDX-License-Identifier: Apache-2.0 // use crate::proto::KeyValue; use std::collections::HashMap; #[derive(Clone, Default, Debug)] pub struct Context { pub metadata: HashMap>, pub timeout_nano: i64, } pub fn with_timeout(i: i64) -> Context { Context { timeout_nano: i, ..Default::default() } } pub fn with_metadata(md: HashMap>) -> Context { Context { metadata: md, ..Default::default() } } impl Context { // appends additional values to the given key. pub fn add(&mut self, key: String, value: String) { if let Some(ref mut vl) = self.metadata.get_mut(&key) { vl.push(value); } else { self.metadata.insert(key.to_lowercase(), vec![value]); } } // Set sets the provided values for a given key. // The values will overwrite any existing values. // If no values provided, a key will be deleted. pub fn set(&mut self, key: String, value: Vec) { if value.is_empty() { self.metadata.remove(&key); } else { self.metadata.insert(key.to_lowercase(), value); } } } pub fn from_pb(kvs: &Vec) -> HashMap> { let mut meta: HashMap> = HashMap::new(); for kv in kvs { if let Some(ref mut vl) = meta.get_mut(&kv.key) { vl.push(kv.value.clone()); } else { meta.insert(kv.key.clone(), vec![kv.value.clone()]); } } meta } pub fn to_pb(kvs: HashMap>) -> Vec { let mut meta = Vec::with_capacity(kvs.len()); for (k, vl) in kvs { for v in vl { let key = KeyValue { key: k.clone(), value: v.clone(), ..Default::default() }; meta.push(key); } } meta } #[cfg(test)] mod tests { use crate::context; use crate::proto::KeyValue; #[test] fn test_metadata() { // RepeatedField -> HashMap, test from_pb() let mut src = Vec::new(); for i in &[ ("key1", "value1-1"), ("key1", "value1-2"), ("key2", "value2"), ] { let key = KeyValue { key: i.0.to_string(), value: i.1.to_string(), ..Default::default() }; src.push(key); } let dst = context::from_pb(&src); assert_eq!(dst.len(), 2); assert_eq!( dst.get("key1"), Some(&vec!["value1-1".to_string(), "value1-2".to_string()]) ); assert_eq!(dst.get("key2"), Some(&vec!["value2".to_string()])); assert_eq!(dst.get("key3"), None); // HashMap -> RepeatedField , test to_pb() let mut kvs = context::to_pb(dst); kvs.sort_by(|a, b| a.key.partial_cmp(&b.key).unwrap()); assert_eq!(kvs.len(), 3); assert_eq!(kvs[0].key, "key1"); assert_eq!(kvs[0].value, "value1-1"); assert_eq!(kvs[1].key, "key1"); assert_eq!(kvs[1].value, "value1-2"); assert_eq!(kvs[2].key, "key2"); assert_eq!(kvs[2].value, "value2"); } #[test] fn test_context() { let ctx: context::Context = Default::default(); assert_eq!(0, ctx.timeout_nano); assert_eq!(ctx.metadata.len(), 0); let mut ctx = context::with_timeout(99); assert_eq!(99, ctx.timeout_nano); assert_eq!(ctx.metadata.len(), 0); ctx.add("key1".to_string(), "value1-1".to_string()); assert_eq!(ctx.metadata.len(), 1); assert_eq!( ctx.metadata.get("key1"), Some(&vec!["value1-1".to_string()]) ); ctx.add("key1".to_string(), "value1-2".to_string()); assert_eq!(ctx.metadata.len(), 1); assert_eq!( ctx.metadata.get("key1"), Some(&vec!["value1-1".to_string(), "value1-2".to_string()]) ); ctx.set("key2".to_string(), vec!["value2".to_string()]); assert_eq!(ctx.metadata.len(), 2); assert_eq!(ctx.metadata.get("key2"), Some(&vec!["value2".to_string()])); ctx.set("key1".to_string(), vec![]); assert_eq!(ctx.metadata.len(), 1); assert_eq!(ctx.metadata.get("key1"), None); } } ttrpc-0.8.1/src/error.rs000064400000000000000000000050631046102023000132600ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. //! Error and Result of ttrpc and relevant functions, macros. use crate::proto::{Code, Response, Status}; use std::result; use thiserror::Error; /// The error type for ttrpc. #[derive(Error, Debug, Clone, PartialEq)] pub enum Error { #[error("socket err: {0}")] Socket(String), #[error("rpc status: {0:?}")] RpcStatus(Status), #[cfg(unix)] #[error("Nix error: {0}")] Nix(#[from] nix::Error), #[cfg(windows)] #[error("Windows error: {0}")] Windows(i32), #[error("ttrpc err: local stream closed")] LocalClosed, #[error("ttrpc err: remote stream closed")] RemoteClosed, #[error("eof")] Eof, #[error("ttrpc err: {0}")] Others(String), } impl From for Response { fn from(e: Error) -> Self { let status = if let Error::RpcStatus(stat) = e { stat } else { get_status(Code::UNKNOWN, e) }; let mut res = Response::new(); res.set_status(status); res } } /// A specialized Result type for ttrpc. pub type Result = result::Result; /// Get ttrpc::Status from ttrpc::Code and a message. pub fn get_status(c: Code, msg: impl ToString) -> Status { let mut status = Status::new(); status.set_code(c); status.set_message(msg.to_string()); status } pub fn get_rpc_status(c: Code, msg: impl ToString) -> Error { Error::RpcStatus(get_status(c, msg)) } const SOCK_DICONNECTED: &str = "socket disconnected"; pub fn sock_error_msg(size: usize, msg: String) -> Error { if size == 0 { return Error::Socket(SOCK_DICONNECTED.to_string()); } get_rpc_status(Code::INVALID_ARGUMENT, msg) } macro_rules! err_to_others_err { ($e: ident, $s: expr) => { |$e| Error::Others($s.to_string() + &$e.to_string()) }; } /// Convert to ttrpc::Error::Others. #[macro_export] macro_rules! err_to_others { ($e: ident, $s: expr) => { |$e| ::ttrpc::Error::Others($s.to_string() + &$e.to_string()) }; } ttrpc-0.8.1/src/lib.rs000064400000000000000000000043341046102023000126750ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. //! ttrpc-rust is a **non-core** subproject of containerd //! //! `ttrpc-rust` is the Rust version of [ttrpc](https://github.com/containerd/ttrpc). [ttrpc](https://github.com/containerd/ttrpc) is GRPC for low-memory environments. //! //! Example: //! //! Check [this](https://github.com/containerd/ttrpc-rust/tree/master/example) //! //! # Feature flags //! //! - `async`: Enables async server and client. //! - `sync`: Enables traditional sync server and client (default enabled). //! - `protobuf-codec`: Includes rust-protobuf (default enabled). //! //! # Socket address //! //! For Linux distributions, ttrpc-rust supports three types of socket: //! //! - `unix:///run/some.sock`: Normal Unix domain socket. //! - `unix://@/run/some.sock`: Abstract Unix domain socket. //! - `vsock://vsock://8:1024`: [vsock](https://man7.org/linux/man-pages/man7/vsock.7.html). //! //! For mscOS, ttrpc-rust **only** supports normal Unix domain socket: //! //! - `unix:///run/some.sock`: Normal Unix domain socket. //! #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] extern crate log; #[macro_use] pub mod error; #[macro_use] mod common; #[macro_use] mod macros; pub mod context; pub mod proto; #[doc(inline)] pub use self::proto::{Code, MessageHeader, Request, Response, Status}; #[doc(inline)] pub use crate::error::{get_status, Error, Result}; cfg_sync! { pub mod sync; #[doc(hidden)] pub use sync::response_to_channel; #[doc(inline)] pub use sync::{MethodHandler, TtrpcContext}; pub use sync::Client; #[doc(inline)] pub use sync::Server; } cfg_async! { pub mod asynchronous; #[doc(hidden)] pub use asynchronous as r#async; } ttrpc-0.8.1/src/macros.rs000064400000000000000000000010151046102023000134040ustar 00000000000000// Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // //! macro functions. macro_rules! cfg_sync { ($($item:item)*) => { $( #[cfg(feature = "sync")] #[cfg_attr(docsrs, doc(cfg(feature = "sync")))] $item )* } } macro_rules! cfg_async { ($($item:item)*) => { $( #[cfg(all(feature = "async", target_family="unix"))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))] $item )* } } ttrpc-0.8.1/src/proto.rs000064400000000000000000000416611046102023000132760ustar 00000000000000// Copyright 2022 Alibaba Cloud. All rights reserved. // Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // #[allow(soft_unstable, clippy::type_complexity, clippy::too_many_arguments)] mod compiled { include!(concat!(env!("OUT_DIR"), "/mod.rs")); } pub use compiled::ttrpc::*; use byteorder::{BigEndian, ByteOrder}; use protobuf::{CodedInputStream, CodedOutputStream}; use crate::error::{get_rpc_status, Error, Result as TtResult}; pub const MESSAGE_HEADER_LENGTH: usize = 10; pub const MESSAGE_LENGTH_MAX: usize = 4 << 20; pub const DEFAULT_PAGE_SIZE: usize = 4 << 10; pub const MESSAGE_TYPE_REQUEST: u8 = 0x1; pub const MESSAGE_TYPE_RESPONSE: u8 = 0x2; pub const MESSAGE_TYPE_DATA: u8 = 0x3; pub const FLAG_REMOTE_CLOSED: u8 = 0x1; pub const FLAG_REMOTE_OPEN: u8 = 0x2; pub const FLAG_NO_DATA: u8 = 0x4; pub(crate) fn check_oversize(len: usize, return_rpc_error: bool) -> TtResult<()> { if len > MESSAGE_LENGTH_MAX { let msg = format!( "message length {} exceed maximum message size of {}", len, MESSAGE_LENGTH_MAX ); let e = if return_rpc_error { get_rpc_status(Code::INVALID_ARGUMENT, msg) } else { Error::Others(msg) }; return Err(e); } Ok(()) } // Discard the unwanted message body #[cfg(feature = "async")] async fn discard_message_body( mut reader: impl tokio::io::AsyncReadExt + Unpin, header: &MessageHeader, ) -> TtResult<()> { let mut need_discard = header.length as usize; while need_discard > 0 { let once_discard = std::cmp::min(DEFAULT_PAGE_SIZE, need_discard); let mut content = vec![0; once_discard]; reader .read_exact(&mut content) .await .map_err(|e| Error::Socket(e.to_string()))?; need_discard -= once_discard; } Ok(()) } /// Message header of ttrpc. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub struct MessageHeader { pub length: u32, pub stream_id: u32, pub type_: u8, pub flags: u8, } impl From for MessageHeader where T: AsRef<[u8]>, { fn from(buf: T) -> Self { let buf = buf.as_ref(); debug_assert!(buf.len() >= MESSAGE_HEADER_LENGTH); Self { length: BigEndian::read_u32(&buf[..4]), stream_id: BigEndian::read_u32(&buf[4..8]), type_: buf[8], flags: buf[9], } } } impl From for Vec { fn from(mh: MessageHeader) -> Self { let mut buf = vec![0u8; MESSAGE_HEADER_LENGTH]; mh.into_buf(&mut buf); buf } } impl MessageHeader { /// Creates a request MessageHeader from stream_id and len. /// /// Use the default message type MESSAGE_TYPE_REQUEST, and default flags 0. pub fn new_request(stream_id: u32, len: u32) -> Self { Self { length: len, stream_id, type_: MESSAGE_TYPE_REQUEST, flags: 0, } } /// Creates a response MessageHeader from stream_id and len. /// /// Use the MESSAGE_TYPE_RESPONSE message type, and default flags 0. pub fn new_response(stream_id: u32, len: u32) -> Self { Self { length: len, stream_id, type_: MESSAGE_TYPE_RESPONSE, flags: 0, } } /// Creates a data MessageHeader from stream_id and len. /// /// Use the MESSAGE_TYPE_DATA message type, and default flags 0. pub fn new_data(stream_id: u32, len: u32) -> Self { Self { length: len, stream_id, type_: MESSAGE_TYPE_DATA, flags: 0, } } /// Set the stream_id of message using the given value. pub fn set_stream_id(&mut self, stream_id: u32) { self.stream_id = stream_id; } /// Set the flags of message using the given flags. pub fn set_flags(&mut self, flags: u8) { self.flags = flags; } /// Add a new flags to the message. pub fn add_flags(&mut self, flags: u8) { self.flags |= flags; } pub(crate) fn into_buf(self, mut buf: impl AsMut<[u8]>) { let buf = buf.as_mut(); debug_assert!(buf.len() >= MESSAGE_HEADER_LENGTH); let covbuf: &mut [u8] = &mut buf[..4]; BigEndian::write_u32(covbuf, self.length); let covbuf: &mut [u8] = &mut buf[4..8]; BigEndian::write_u32(covbuf, self.stream_id); buf[8] = self.type_; buf[9] = self.flags; } } #[cfg(feature = "async")] impl MessageHeader { /// Encodes a MessageHeader to writer. pub async fn write_to( &self, mut writer: impl tokio::io::AsyncWriteExt + Unpin, ) -> std::io::Result<()> { writer.write_u32(self.length).await?; writer.write_u32(self.stream_id).await?; writer.write_u8(self.type_).await?; writer.write_u8(self.flags).await?; writer.flush().await } /// Decodes a MessageHeader from reader. pub async fn read_from( mut reader: impl tokio::io::AsyncReadExt + Unpin, ) -> std::io::Result { let mut content = vec![0; MESSAGE_HEADER_LENGTH]; reader.read_exact(&mut content).await?; Ok(MessageHeader::from(&content)) } } /// Generic message of ttrpc. #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct GenMessage { pub header: MessageHeader, pub payload: Vec, } #[derive(Debug, PartialEq)] pub enum GenMessageError { InternalError(Error), ReturnError(MessageHeader, Error), } impl From for GenMessageError { fn from(e: Error) -> Self { Self::InternalError(e) } } #[cfg(feature = "async")] impl GenMessage { /// Encodes a MessageHeader to writer. pub async fn write_to( &self, mut writer: impl tokio::io::AsyncWriteExt + Unpin, ) -> TtResult<()> { self.header .write_to(&mut writer) .await .map_err(|e| Error::Socket(e.to_string()))?; writer .write_all(&self.payload) .await .map_err(|e| Error::Socket(e.to_string()))?; Ok(()) } /// Decodes a MessageHeader from reader. pub async fn read_from( mut reader: impl tokio::io::AsyncReadExt + Unpin, ) -> std::result::Result { let header = MessageHeader::read_from(&mut reader) .await .map_err(|e| Error::Socket(e.to_string()))?; if let Err(e) = check_oversize(header.length as usize, true) { discard_message_body(reader, &header).await?; return Err(GenMessageError::ReturnError(header, e)); } let mut content = vec![0; header.length as usize]; reader .read_exact(&mut content) .await .map_err(|e| Error::Socket(e.to_string()))?; Ok(Self { header, payload: content, }) } pub fn check(&self) -> TtResult<()> { check_oversize(self.header.length as usize, true) } } /// TTRPC codec, only protobuf is supported. pub trait Codec { type E; fn size(&self) -> u32; fn encode(&self) -> Result, Self::E>; fn decode(buf: impl AsRef<[u8]>) -> Result where Self: Sized; } impl Codec for M { type E = protobuf::Error; fn size(&self) -> u32 { self.compute_size() as u32 } fn encode(&self) -> Result, Self::E> { let mut buf = vec![0; self.compute_size() as usize]; let mut s = CodedOutputStream::bytes(&mut buf); self.write_to(&mut s)?; s.flush()?; drop(s); Ok(buf) } fn decode(buf: impl AsRef<[u8]>) -> Result { let mut s = CodedInputStream::from_bytes(buf.as_ref()); M::parse_from(&mut s) } } /// Message of ttrpc. #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Message { pub header: MessageHeader, pub payload: C, } impl std::convert::TryFrom for Message where C: Codec, { type Error = C::E; fn try_from(gen: GenMessage) -> Result { Ok(Self { header: gen.header, payload: C::decode(&gen.payload)?, }) } } impl std::convert::TryFrom> for GenMessage where C: Codec, { type Error = C::E; fn try_from(msg: Message) -> Result { Ok(Self { header: msg.header, payload: msg.payload.encode()?, }) } } impl Message { pub fn new_request(stream_id: u32, message: C) -> TtResult { check_oversize(message.size() as usize, false)?; Ok(Self { header: MessageHeader::new_request(stream_id, message.size()), payload: message, }) } } #[cfg(feature = "async")] impl Message where C: Codec, C::E: std::fmt::Display, { /// Encodes a MessageHeader to writer. pub async fn write_to( &self, mut writer: impl tokio::io::AsyncWriteExt + Unpin, ) -> TtResult<()> { self.header .write_to(&mut writer) .await .map_err(|e| Error::Socket(e.to_string()))?; let content = self .payload .encode() .map_err(err_to_others_err!(e, "Encode payload failed."))?; writer .write_all(&content) .await .map_err(|e| Error::Socket(e.to_string()))?; Ok(()) } /// Decodes a MessageHeader from reader. pub async fn read_from(mut reader: impl tokio::io::AsyncReadExt + Unpin) -> TtResult { let header = MessageHeader::read_from(&mut reader) .await .map_err(|e| Error::Socket(e.to_string()))?; if check_oversize(header.length as usize, true).is_err() { discard_message_body(reader, &header).await?; return Ok(Self { header, payload: C::decode("").map_err(err_to_others_err!(e, "Decode payload failed."))?, }); } let mut content = vec![0; header.length as usize]; reader .read_exact(&mut content) .await .map_err(|e| Error::Socket(e.to_string()))?; let payload = C::decode(content).map_err(err_to_others_err!(e, "Decode payload failed."))?; Ok(Self { header, payload }) } } #[cfg(test)] mod tests { use std::convert::{TryFrom, TryInto}; use super::*; static MESSAGE_HEADER: [u8; MESSAGE_HEADER_LENGTH] = [ 0x10, 0x0, 0x0, 0x0, // length 0x0, 0x0, 0x0, 0x03, // stream_id 0x2, // type_ 0xef, // flags ]; #[test] fn message_header() { let mh = MessageHeader::from(&MESSAGE_HEADER); assert_eq!(mh.length, 0x1000_0000); assert_eq!(mh.stream_id, 0x3); assert_eq!(mh.type_, MESSAGE_TYPE_RESPONSE); assert_eq!(mh.flags, 0xef); let mut buf2 = vec![0; MESSAGE_HEADER_LENGTH]; mh.into_buf(&mut buf2); assert_eq!(&MESSAGE_HEADER, &buf2[..]); let mh = MessageHeader::from(&PROTOBUF_MESSAGE_HEADER); assert_eq!(mh.length as usize, TEST_PAYLOAD_LEN); } #[rustfmt::skip] static PROTOBUF_MESSAGE_HEADER: [u8; MESSAGE_HEADER_LENGTH] = [ 0x00, 0x0, 0x0, TEST_PAYLOAD_LEN as u8, // length 0x0, 0x12, 0x34, 0x56, // stream_id 0x1, // type_ 0xef, // flags ]; const TEST_PAYLOAD_LEN: usize = 67; static PROTOBUF_REQUEST: [u8; TEST_PAYLOAD_LEN] = [ 10, 17, 103, 114, 112, 99, 46, 84, 101, 115, 116, 83, 101, 114, 118, 105, 99, 101, 115, 18, 4, 84, 101, 115, 116, 26, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 32, 128, 218, 196, 9, 42, 24, 10, 9, 116, 101, 115, 116, 95, 107, 101, 121, 49, 18, 11, 116, 101, 115, 116, 95, 118, 97, 108, 117, 101, 49, ]; fn new_protobuf_request() -> Request { let mut creq = Request::new(); creq.set_service("grpc.TestServices".to_string()); creq.set_method("Test".to_string()); creq.set_timeout_nano(20 * 1000 * 1000); let meta = vec![KeyValue { key: "test_key1".to_string(), value: "test_value1".to_string(), ..Default::default() }]; creq.set_metadata(meta); creq.payload = vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9]; creq } #[test] fn protobuf_codec() { let creq = new_protobuf_request(); let buf = creq.encode().unwrap(); assert_eq!(&buf, &PROTOBUF_REQUEST); let dreq = Request::decode(&buf).unwrap(); assert_eq!(creq, dreq); let dreq2 = Request::decode(PROTOBUF_REQUEST).unwrap(); assert_eq!(creq, dreq2); } #[test] fn gen_message_to_message() { let req = new_protobuf_request(); let msg = Message::new_request(3, req).unwrap(); let msg_clone = msg.clone(); let gen: GenMessage = msg.try_into().unwrap(); let dmsg = Message::::try_from(gen).unwrap(); assert_eq!(msg_clone, dmsg); } #[cfg(feature = "async")] #[tokio::test] async fn async_message_header() { use std::io::Cursor; let mut buf = vec![]; let mut io = Cursor::new(&mut buf); let mh = MessageHeader::from(&MESSAGE_HEADER); mh.write_to(&mut io).await.unwrap(); assert_eq!(buf, &MESSAGE_HEADER); let dmh = MessageHeader::read_from(&buf[..]).await.unwrap(); assert_eq!(mh, dmh); } #[cfg(feature = "async")] #[tokio::test] async fn async_gen_message() { // Test packet which exceeds maximum message size let mut buf = Vec::from(MESSAGE_HEADER); let header = MessageHeader::read_from(&*buf).await.expect("read header"); buf.append(&mut vec![0x0; header.length as usize]); match GenMessage::read_from(&*buf).await { Err(GenMessageError::ReturnError(h, Error::RpcStatus(s))) => { if h != header || s.code() != crate::proto::Code::INVALID_ARGUMENT { panic!("got invalid error when the size exceeds limit"); } } _ => { panic!("got invalid error when the size exceeds limit"); } } let mut buf = Vec::from(PROTOBUF_MESSAGE_HEADER); buf.extend_from_slice(&PROTOBUF_REQUEST); buf.extend_from_slice(&[0x0, 0x0]); let gen = GenMessage::read_from(&*buf).await.unwrap(); assert_eq!(gen.header.length as usize, TEST_PAYLOAD_LEN); assert_eq!(gen.header.length, gen.payload.len() as u32); assert_eq!(gen.header.stream_id, 0x123456); assert_eq!(gen.header.type_, MESSAGE_TYPE_REQUEST); assert_eq!(gen.header.flags, 0xef); assert_eq!(&gen.payload, &PROTOBUF_REQUEST); assert_eq!( &buf[MESSAGE_HEADER_LENGTH + TEST_PAYLOAD_LEN..], &[0x0, 0x0] ); let mut dbuf = vec![]; let mut io = std::io::Cursor::new(&mut dbuf); gen.write_to(&mut io).await.unwrap(); assert_eq!(&*dbuf, &buf[..MESSAGE_HEADER_LENGTH + TEST_PAYLOAD_LEN]); } #[cfg(feature = "async")] #[tokio::test] async fn async_message() { // Test packet which exceeds maximum message size let mut buf = Vec::from(MESSAGE_HEADER); let header = MessageHeader::read_from(&*buf).await.expect("read header"); buf.append(&mut vec![0x0; header.length as usize]); let gen = Message::::read_from(&*buf) .await .expect("read message"); assert_eq!(gen.header, header); assert_eq!(protobuf::Message::compute_size(&gen.payload), 0); let mut buf = Vec::from(PROTOBUF_MESSAGE_HEADER); buf.extend_from_slice(&PROTOBUF_REQUEST); buf.extend_from_slice(&[0x0, 0x0]); let msg = Message::::read_from(&*buf).await.unwrap(); assert_eq!(msg.header.length, 67); assert_eq!(msg.header.length, msg.payload.size()); assert_eq!(msg.header.stream_id, 0x123456); assert_eq!(msg.header.type_, MESSAGE_TYPE_REQUEST); assert_eq!(msg.header.flags, 0xef); assert_eq!(&msg.payload.service, "grpc.TestServices"); assert_eq!(&msg.payload.method, "Test"); assert_eq!( msg.payload.payload, vec![0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9] ); assert_eq!(msg.payload.timeout_nano, 20 * 1000 * 1000); assert_eq!(msg.payload.metadata.len(), 1); assert_eq!(&msg.payload.metadata[0].key, "test_key1"); assert_eq!(&msg.payload.metadata[0].value, "test_value1"); let req = new_protobuf_request(); let mut dmsg = Message::new_request(u32::MAX, req).unwrap(); dmsg.header.set_stream_id(0x123456); dmsg.header.set_flags(0xe0); dmsg.header.add_flags(0x0f); let mut dbuf = vec![]; let mut io = std::io::Cursor::new(&mut dbuf); dmsg.write_to(&mut io).await.unwrap(); assert_eq!(&dbuf, &buf[..MESSAGE_HEADER_LENGTH + TEST_PAYLOAD_LEN]); } } ttrpc-0.8.1/src/sync/channel.rs000064400000000000000000000075711046102023000145210ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. use crate::error::{sock_error_msg, Error, Result}; use crate::proto::{check_oversize, MessageHeader, DEFAULT_PAGE_SIZE, MESSAGE_HEADER_LENGTH}; use crate::sync::sys::PipeConnection; fn read_count(conn: &PipeConnection, count: usize) -> Result> { let mut v: Vec = vec![0; count]; let mut len = 0; if count == 0 { return Ok(v.to_vec()); } loop { match conn.read(&mut v[len..]) { Ok(l) => { len += l; // when socket peer closed, it would return 0. if len == count || l == 0 { break; } } Err(e) => { return Err(Error::Socket(e.to_string())); } } } Ok(v[0..len].to_vec()) } fn write_count(conn: &PipeConnection, buf: &[u8], count: usize) -> Result { let mut len = 0; if count == 0 { return Ok(0); } loop { match conn.write(&buf[len..]) { Ok(l) => { len += l; if len == count { break; } } Err(e) => { return Err(Error::Socket(e.to_string())); } } } Ok(len) } fn discard_count(conn: &PipeConnection, count: usize) -> Result<()> { let mut need_discard = count; while need_discard > 0 { let once_discard = std::cmp::min(DEFAULT_PAGE_SIZE, need_discard); read_count(conn, once_discard)?; need_discard -= once_discard; } Ok(()) } fn read_message_header(conn: &PipeConnection) -> Result { let buf = read_count(conn, MESSAGE_HEADER_LENGTH)?; let size = buf.len(); if size != MESSAGE_HEADER_LENGTH { return Err(sock_error_msg( size, format!("Message header length {size} is too small"), )); } let mh = MessageHeader::from(&buf); Ok(mh) } pub fn read_message(conn: &PipeConnection) -> Result<(MessageHeader, Result>)> { let mh = read_message_header(conn)?; trace!("Got Message header {:?}", mh); let mh_len = mh.length as usize; if let Err(e) = check_oversize(mh_len, true) { discard_count(conn, mh_len)?; return Ok((mh, Err(e))); } let buf = read_count(conn, mh.length as usize)?; let size = buf.len(); if size != mh.length as usize { return Err(sock_error_msg( size, format!("Message length {} is not {}", size, mh.length), )); } trace!("Got Message body {:?}", buf); Ok((mh, Ok(buf))) } fn write_message_header(conn: &PipeConnection, mh: MessageHeader) -> Result<()> { let buf: Vec = mh.into(); let size = write_count(conn, &buf, MESSAGE_HEADER_LENGTH)?; if size != MESSAGE_HEADER_LENGTH { return Err(sock_error_msg( size, format!("Send Message header length size {size} is not right"), )); } Ok(()) } pub fn write_message(conn: &PipeConnection, mh: MessageHeader, buf: Vec) -> Result<()> { write_message_header(conn, mh)?; let size = write_count(conn, &buf, buf.len())?; if size != buf.len() { return Err(sock_error_msg( size, format!("Send Message length size {size} is not right"), )); } Ok(()) } ttrpc-0.8.1/src/sync/client.rs000064400000000000000000000170541046102023000143640ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. //! Sync client of ttrpc. #[cfg(unix)] use std::os::unix::io::RawFd; use protobuf::Message; use std::collections::HashMap; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use crate::error::{Error, Result}; use crate::proto::{ check_oversize, Code, Codec, MessageHeader, Request, Response, MESSAGE_TYPE_RESPONSE, }; use crate::sync::channel::{read_message, write_message}; use crate::sync::sys::ClientConnection; #[cfg(windows)] use super::sys::PipeConnection; type Sender = mpsc::Sender<(Vec, mpsc::SyncSender>>)>; type Receiver = mpsc::Receiver<(Vec, mpsc::SyncSender>>)>; type ReciverMap = Arc>>>>>; /// A ttrpc Client (sync). #[derive(Clone)] pub struct Client { _connection: Arc, sender_tx: Sender, } impl Client { pub fn connect(sockaddr: &str) -> Result { let conn = ClientConnection::client_connect(sockaddr)?; Self::new_client(conn) } #[cfg(unix)] /// Initialize a new [`Client`] from raw file descriptor. pub fn new(fd: RawFd) -> Result { let conn = ClientConnection::new(fd); Self::new_client(conn) } fn new_client(pipe_client: ClientConnection) -> Result { let client = Arc::new(pipe_client); let (sender_tx, rx): (Sender, Receiver) = mpsc::channel(); let recver_map_orig = Arc::new(Mutex::new(HashMap::new())); let receiver_map = recver_map_orig.clone(); let connection = Arc::new(client.get_pipe_connection()?); let sender_client = connection.clone(); //Sender thread::spawn(move || { let mut stream_id: u32 = 1; for (buf, recver_tx) in rx.iter() { let current_stream_id = stream_id; stream_id += 2; //Put current_stream_id and recver_tx to recver_map { let mut map = receiver_map.lock().unwrap(); map.insert(current_stream_id, recver_tx.clone()); } let mut mh = MessageHeader::new_request(0, buf.len() as u32); mh.set_stream_id(current_stream_id); if let Err(e) = write_message(&sender_client, mh, buf) { //Remove current_stream_id and recver_tx to recver_map { let mut map = receiver_map.lock().unwrap(); map.remove(¤t_stream_id); } recver_tx .send(Err(e)) .unwrap_or_else(|_e| error!("The request has returned")); } } trace!("Sender quit"); }); //Recver let receiver_connection = connection; let receiver_client = client.clone(); thread::spawn(move || { loop { match receiver_client.ready() { Ok(None) => { continue; } Ok(_) => {} Err(e) => { error!("pipeConnection ready error {:?}", e); break; } } match read_message(&receiver_connection) { Ok((mh, buf)) => { trans_resp(recver_map_orig.clone(), mh, buf); } Err(x) => match x { Error::Socket(y) => { trace!("Socket error {}", y); let mut map = recver_map_orig.lock().unwrap(); for (_, recver_tx) in map.iter_mut() { recver_tx .send(Err(Error::Socket(format!("socket error {y}")))) .unwrap_or_else(|e| { error!("The request has returned error {:?}", e) }); } map.clear(); break; } _ => { trace!("Others error {:?}", x); continue; } }, }; } let _ = receiver_client .close_receiver() .map_err(|e| warn!("failed to close with error: {:?}", e)); trace!("Receiver quit"); }); Ok(Client { _connection: client, sender_tx, }) } pub fn request(&self, req: Request) -> Result { check_oversize(req.compute_size() as usize, false)?; let buf = req.encode().map_err(err_to_others_err!(e, ""))?; // Notice: pure client problem can't be rpc error let (tx, rx) = mpsc::sync_channel(0); self.sender_tx .send((buf, tx)) .map_err(err_to_others_err!(e, "Send packet to sender error "))?; let result = if req.timeout_nano == 0 { rx.recv().map_err(err_to_others_err!( e, "Receive packet from Receiver error: " ))? } else { rx.recv_timeout(Duration::from_nanos(req.timeout_nano as u64)) .map_err(err_to_others_err!( e, "Receive packet from Receiver timeout: " ))? }; let buf = result?; let res = Response::decode(buf).map_err(err_to_others_err!(e, "Unpack response error "))?; let status = res.status(); if status.code() != Code::OK { return Err(Error::RpcStatus((*status).clone())); } Ok(res) } } impl Drop for ClientConnection { fn drop(&mut self) { self.close().unwrap(); trace!("Client is dropped"); } } // close everything up from the pipe connection on Windows #[cfg(windows)] impl Drop for PipeConnection { fn drop(&mut self) { self.close() .unwrap_or_else(|e| trace!("connection may already be closed: {}", e)); trace!("pipe connection is dropped"); } } /// Transfer the response fn trans_resp(recver_map_orig: ReciverMap, mh: MessageHeader, buf: Result>) { let mut map = recver_map_orig.lock().unwrap(); let recver_tx = match map.get(&mh.stream_id) { Some(tx) => tx, None => { debug!("Recver got unknown packet {:?} {:?}", mh, buf); return; } }; if mh.type_ != MESSAGE_TYPE_RESPONSE { recver_tx .send(Err(Error::Others(format!( "Recver got malformed packet {:?} {:?}", mh, buf )))) .unwrap_or_else(|_e| error!("The request has returned")); return; } recver_tx .send(buf) .unwrap_or_else(|_e| error!("The request has returned")); map.remove(&mh.stream_id); } ttrpc-0.8.1/src/sync/mod.rs000064400000000000000000000005211046102023000136540ustar 00000000000000// Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // //! Server and Client in sync mode. mod channel; mod client; mod server; mod sys; #[macro_use] mod utils; pub use client::Client; pub use server::Server; #[doc(hidden)] pub use utils::response_to_channel; pub use utils::{MethodHandler, TtrpcContext}; ttrpc-0.8.1/src/sync/server.rs000064400000000000000000000536571046102023000144250ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. //! Sync server of ttrpc. //! #[cfg(unix)] use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use protobuf::{CodedInputStream, Message}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}; use std::sync::{Arc, Mutex}; use std::thread; use std::thread::JoinHandle; use super::utils::{response_error_to_channel, response_to_channel}; use crate::context; use crate::error::{get_status, Error, Result}; use crate::proto::{Code, MessageHeader, Request, Response, MESSAGE_TYPE_REQUEST}; use crate::sync::channel::{read_message, write_message}; use crate::sync::sys::{PipeConnection, PipeListener}; use crate::{MethodHandler, TtrpcContext}; // poll_queue will create WAIT_THREAD_COUNT_DEFAULT threads in begin. // If wait thread count < WAIT_THREAD_COUNT_MIN, create number to WAIT_THREAD_COUNT_DEFAULT. // If wait thread count > WAIT_THREAD_COUNT_MAX, wait thread will quit to WAIT_THREAD_COUNT_DEFAULT. const DEFAULT_WAIT_THREAD_COUNT_DEFAULT: usize = 3; const DEFAULT_WAIT_THREAD_COUNT_MIN: usize = 1; const DEFAULT_WAIT_THREAD_COUNT_MAX: usize = 5; type MessageSender = Sender<(MessageHeader, Vec)>; type MessageReceiver = Receiver<(MessageHeader, Vec)>; type WorkloadSender = crossbeam::channel::Sender<(MessageHeader, Result>)>; type WorkloadReceiver = crossbeam::channel::Receiver<(MessageHeader, Result>)>; /// A ttrpc Server (sync). pub struct Server { listeners: Vec>, listener_quit_flag: Arc, connections: Arc>>, methods: Arc>>, handler: Option>, reaper: Option<(Sender, JoinHandle<()>)>, thread_count_default: usize, thread_count_min: usize, thread_count_max: usize, } struct Connection { connection: Arc, quit: Arc, handler: Option>, } impl Connection { fn close(&self) { self.connection.close().unwrap_or(()); } fn shutdown(&self) { self.quit.store(true, Ordering::SeqCst); // in case the connection had closed self.connection.shutdown().unwrap_or(()); } } struct ThreadS<'a> { connection: &'a Arc, workload_rx: &'a WorkloadReceiver, wtc: &'a Arc, quit: &'a Arc, methods: &'a Arc>>, res_tx: &'a MessageSender, control_tx: &'a SyncSender<()>, cancel_rx: &'a crossbeam::channel::Receiver<()>, default: usize, min: usize, max: usize, } #[allow(clippy::too_many_arguments)] fn start_method_handler_thread( connection: Arc, workload_rx: WorkloadReceiver, wtc: Arc, quit: Arc, methods: Arc>>, res_tx: MessageSender, control_tx: SyncSender<()>, cancel_rx: crossbeam::channel::Receiver<()>, min: usize, max: usize, ) { thread::spawn(move || { while !quit.load(Ordering::SeqCst) { let c = wtc.fetch_add(1, Ordering::SeqCst) + 1; if c > max { wtc.fetch_sub(1, Ordering::SeqCst); break; } let result = workload_rx.recv(); if quit.load(Ordering::SeqCst) { // notify the connection dealing main thread to stop. control_tx .send(()) .unwrap_or_else(|err| trace!("Failed to send {:?}", err)); break; } let c = wtc.fetch_sub(1, Ordering::SeqCst) - 1; if c < min { trace!("notify client handler to create much more worker threads!"); control_tx .send(()) .unwrap_or_else(|err| trace!("Failed to send {:?}", err)); } let mh; let buf; match result { Ok((x, Ok(y))) => { mh = x; buf = y; } Ok((mh, Err(e))) => { if let Err(x) = response_error_to_channel(mh.stream_id, e, res_tx.clone()) { debug!("response_error_to_channel get error {:?}", x); quit_connection(quit, control_tx); break; } continue; } Err(x) => match x { crossbeam::channel::RecvError => { trace!("workload_rx recv error"); quit_connection(quit, control_tx); trace!("workload_rx recv error, send control_tx"); break; } }, } if mh.type_ != MESSAGE_TYPE_REQUEST { continue; } let mut s = CodedInputStream::from_bytes(&buf); let mut req = Request::new(); if let Err(x) = req.merge_from(&mut s) { let status = get_status(Code::INVALID_ARGUMENT, x.to_string()); let mut res = Response::new(); res.set_status(status); if let Err(x) = response_to_channel(mh.stream_id, res, res_tx.clone()) { debug!("response_to_channel get error {:?}", x); quit_connection(quit, control_tx); break; } continue; } trace!("Got Message request {:?}", req); let path = format!("/{}/{}", req.service, req.method); let method = if let Some(x) = methods.get(&path) { x } else { let status = get_status(Code::INVALID_ARGUMENT, format!("{path} does not exist")); let mut res = Response::new(); res.set_status(status); if let Err(x) = response_to_channel(mh.stream_id, res, res_tx.clone()) { info!("response_to_channel get error {:?}", x); quit_connection(quit, control_tx); break; } continue; }; let ctx = TtrpcContext { fd: connection.id(), cancel_rx: cancel_rx.clone(), mh, res_tx: res_tx.clone(), metadata: context::from_pb(&req.metadata), timeout_nano: req.timeout_nano, }; if let Err(x) = method.handler(ctx, req) { debug!("method handle {} get error {:?}", path, x); quit_connection(quit, control_tx); break; } } }); } fn start_method_handler_threads(num: usize, ts: &ThreadS) { for _ in 0..num { if ts.quit.load(Ordering::SeqCst) { break; } start_method_handler_thread( ts.connection.clone(), ts.workload_rx.clone(), ts.wtc.clone(), ts.quit.clone(), ts.methods.clone(), ts.res_tx.clone(), ts.control_tx.clone(), ts.cancel_rx.clone(), ts.min, ts.max, ); } } fn check_method_handler_threads(ts: &ThreadS) { let c = ts.wtc.load(Ordering::SeqCst); if c < ts.min { start_method_handler_threads(ts.default - c, ts); } } impl Default for Server { fn default() -> Self { Server { listeners: Vec::with_capacity(1), listener_quit_flag: Arc::new(AtomicBool::new(false)), connections: Arc::new(Mutex::new(HashMap::new())), methods: Arc::new(HashMap::new()), handler: None, reaper: None, thread_count_default: DEFAULT_WAIT_THREAD_COUNT_DEFAULT, thread_count_min: DEFAULT_WAIT_THREAD_COUNT_MIN, thread_count_max: DEFAULT_WAIT_THREAD_COUNT_MAX, } } } impl Server { pub fn new() -> Server { Server::default() } pub fn bind(mut self, sockaddr: &str) -> Result { if !self.listeners.is_empty() { return Err(Error::Others( "ttrpc-rust just support 1 sockaddr now".to_string(), )); } let listener = PipeListener::new(sockaddr)?; self.listeners.push(Arc::new(listener)); Ok(self) } #[cfg(unix)] pub fn add_listener(mut self, fd: RawFd) -> Result { if !self.listeners.is_empty() { return Err(Error::Others( "ttrpc-rust just support 1 sockaddr now".to_string(), )); } let listener = PipeListener::new_from_fd(fd)?; self.listeners.push(Arc::new(listener)); Ok(self) } pub fn register_service( mut self, methods: HashMap>, ) -> Server { let mut_methods = Arc::get_mut(&mut self.methods).unwrap(); mut_methods.extend(methods); self } pub fn set_thread_count_default(mut self, count: usize) -> Server { self.thread_count_default = count; self } pub fn set_thread_count_min(mut self, count: usize) -> Server { self.thread_count_min = count; self } pub fn set_thread_count_max(mut self, count: usize) -> Server { self.thread_count_max = count; self } pub fn start_listen(&mut self) -> Result<()> { let connections = self.connections.clone(); if self.listeners.is_empty() { return Err(Error::Others("ttrpc-rust not bind".to_string())); } self.listener_quit_flag.store(false, Ordering::SeqCst); let listener = self.listeners[0].clone(); let methods = self.methods.clone(); let default = self.thread_count_default; let min = self.thread_count_min; let max = self.thread_count_max; let listener_quit_flag = self.listener_quit_flag.clone(); let reaper_tx = match self.reaper.take() { None => { let reaper_connections = connections.clone(); let (reaper_tx, reaper_rx) = channel(); let reaper_handler = thread::Builder::new() .name("reaper".into()) .spawn(move || { for fd in reaper_rx.iter() { reaper_connections .lock() .unwrap() .remove(&fd) .map(|mut cn| { cn.handler.take().map(|handler| { handler.join().unwrap(); cn.close() }) }); } info!("reaper thread exited"); }) .unwrap(); self.reaper = Some((reaper_tx.clone(), reaper_handler)); reaper_tx } Some(r) => { let reaper_tx = r.0.clone(); self.reaper = Some(r); reaper_tx } }; let handler = thread::Builder::new() .name("listener_loop".into()) .spawn(move || { loop { trace!("listening..."); let pipe_connection = match listener.accept(&listener_quit_flag) { Ok(None) => { continue; } Ok(Some(conn)) => Arc::new(conn), Err(e) => { error!("listener accept got {:?}", e); break; } }; let methods = methods.clone(); let quit = Arc::new(AtomicBool::new(false)); let child_quit = quit.clone(); let reaper_tx_child = reaper_tx.clone(); let pipe_connection_child = pipe_connection.clone(); let handler = thread::Builder::new() .name("client_handler".into()) .spawn(move || { debug!("Got new client"); // Start response thread let quit_res = child_quit.clone(); let pipe = pipe_connection_child.clone(); let (res_tx, res_rx): (MessageSender, MessageReceiver) = channel(); let handler = thread::spawn(move || { for r in res_rx.iter() { trace!("response thread get {:?}", r); if let Err(e) = write_message(&pipe, r.0, r.1) { error!("write_message got {:?}", e); quit_res.store(true, Ordering::SeqCst); break; } } trace!("response thread quit"); }); let (control_tx, control_rx): (SyncSender<()>, Receiver<()>) = sync_channel(0); // start read message thread let quit_reader = child_quit.clone(); let pipe_reader = pipe_connection_child.clone(); let (workload_tx, workload_rx): (WorkloadSender, WorkloadReceiver) = crossbeam::channel::unbounded(); let (cancel_tx, cancel_rx) = crossbeam::channel::unbounded::<()>(); let control_tx_reader = control_tx.clone(); let reader = thread::spawn(move || { while !quit_reader.load(Ordering::SeqCst) { let msg = read_message(&pipe_reader); match msg { Ok((x, y)) => { let res = workload_tx.send((x, y)); match res { Ok(_) => {} Err(crossbeam::channel::SendError(e)) => { error!("Send workload error {:?}", e); quit_reader.store(true, Ordering::SeqCst); control_tx_reader.send(()).unwrap_or_else( |err| trace!("Failed to send {:?}", err), ); break; } } } Err(x) => match x { Error::Socket(y) => { trace!("Socket error {}", y); drop(cancel_tx); quit_reader.store(true, Ordering::SeqCst); // the client connection would be closed and // the connection dealing main thread would // have exited. control_tx_reader.send(()).unwrap_or_else(|err| { trace!("Failed to send {:?}", err) }); trace!("Socket error send control_tx"); break; } _ => { trace!("Other error {:?}", x); continue; } }, } } trace!("read message thread quit"); }); let pipe = pipe_connection_child.clone(); let ts = ThreadS { connection: &pipe, workload_rx: &workload_rx, wtc: &Arc::new(AtomicUsize::new(0)), methods: &methods, res_tx: &res_tx, control_tx: &control_tx, cancel_rx: &cancel_rx, quit: &child_quit, default, min, max, }; start_method_handler_threads(ts.default, &ts); while !child_quit.load(Ordering::SeqCst) { check_method_handler_threads(&ts); if control_rx.recv().is_err() { break; } } // drop the control_rx, thus all of the method handler threads would // terminated. drop(control_rx); // drop the res_tx, thus the res_rx would get terminated notification. drop(res_tx); drop(workload_rx); handler.join().unwrap_or(()); reader.join().unwrap_or(()); // client_handler should not close fd before exit // , which prevent fd reuse issue. reaper_tx_child.send(pipe.id()).unwrap(); debug!("client thread quit"); }) .unwrap(); let mut cns = connections.lock().unwrap(); let id = pipe_connection.id(); cns.insert( id, Connection { connection: pipe_connection, handler: Some(handler), quit: quit.clone(), }, ); } // end loop // notify reaper thread to exit. drop(reaper_tx); info!("ttrpc server listener stopped"); }) .unwrap(); self.handler = Some(handler); info!("server listen started"); Ok(()) } pub fn start(&mut self) -> Result<()> { if self.thread_count_default >= self.thread_count_max { return Err(Error::Others( "thread_count_default should smaller than thread_count_max".to_string(), )); } if self.thread_count_default <= self.thread_count_min { return Err(Error::Others( "thread_count_default should bigger than thread_count_min".to_string(), )); } self.start_listen()?; info!("server started"); Ok(()) } pub fn stop_listen(mut self) -> Self { self.listener_quit_flag.store(true, Ordering::SeqCst); self.listeners[0] .close() .unwrap_or_else(|e| warn!("failed to close connection with error: {}", e)); info!("close monitor"); if let Some(handler) = self.handler.take() { handler.join().unwrap(); } info!("listener thread stopped"); self } pub fn disconnect(mut self) { info!("begin to shutdown connection"); let connections = self.connections.lock().unwrap(); for (_fd, c) in connections.iter() { c.shutdown(); } // release connections's lock, since the following handler.join() // would wait on the other thread's exit in which would take the lock. drop(connections); info!("connections closed"); if let Some(r) = self.reaper.take() { drop(r.0); r.1.join().unwrap(); } info!("reaper thread stopped"); } pub fn shutdown(self) { self.stop_listen().disconnect(); } } #[cfg(unix)] impl FromRawFd for Server { unsafe fn from_raw_fd(fd: RawFd) -> Self { Self::default().add_listener(fd).unwrap() } } #[cfg(unix)] impl AsRawFd for Server { fn as_raw_fd(&self) -> RawFd { self.listeners[0].as_raw_fd() } } fn quit_connection(quit: Arc, control_tx: SyncSender<()>) { quit.store(true, Ordering::SeqCst); // the client connection would be closed and // the connection dealing main thread would // have exited. control_tx .send(()) .unwrap_or_else(|err| debug!("Failed to send {:?}", err)); } ttrpc-0.8.1/src/sync/sys/mod.rs000064400000000000000000000003711046102023000144750ustar 00000000000000#[cfg(unix)] mod unix; #[cfg(unix)] pub use crate::sync::sys::unix::{PipeConnection, PipeListener, ClientConnection}; #[cfg(windows)] mod windows; #[cfg(windows)] pub use crate::sync::sys::windows::{PipeConnection, PipeListener, ClientConnection}; ttrpc-0.8.1/src/sync/sys/unix/mod.rs000064400000000000000000000001101046102023000154470ustar 00000000000000mod net; pub use net::{PipeConnection, PipeListener, ClientConnection}; ttrpc-0.8.1/src/sync/sys/unix/net.rs000064400000000000000000000223641046102023000154750ustar 00000000000000/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ use crate::error::Result; use nix::sys::socket::*; use std::io::{self}; use std::os::unix::io::RawFd; use std::os::unix::prelude::AsRawFd; use nix::Error; use nix::unistd::*; use std::sync::{Arc}; use std::sync::atomic::{AtomicBool, Ordering}; use crate::common::{self, client_connect, SOCK_CLOEXEC}; #[cfg(target_os = "macos")] use crate::common::set_fd_close_exec; use nix::sys::socket::{self}; pub struct PipeListener { fd: RawFd, monitor_fd: (RawFd, RawFd), } impl AsRawFd for PipeListener { fn as_raw_fd(&self) -> RawFd { self.fd } } impl PipeListener { pub(crate) fn new(sockaddr: &str) -> Result { let (fd, _) = common::do_bind(sockaddr)?; common::do_listen(fd)?; let fds = PipeListener::new_monitor_fd()?; Ok(PipeListener { fd, monitor_fd: fds, }) } pub(crate) fn new_from_fd(fd: RawFd) -> Result { let fds = PipeListener::new_monitor_fd()?; Ok(PipeListener { fd, monitor_fd: fds, }) } fn new_monitor_fd() -> Result<(i32, i32)> { #[cfg(target_os = "linux")] let fds = pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; #[cfg(target_os = "macos")] let fds = { let (rfd, wfd) = pipe()?; set_fd_close_exec(rfd)?; set_fd_close_exec(wfd)?; (rfd, wfd) }; Ok(fds) } // accept returns: // - Ok(Some(PipeConnection)) if a new connection is established // - Ok(None) if spurious wake up with no new connection // - Err(io::Error) if there is an error and listener loop should be shutdown pub(crate) fn accept( &self, quit_flag: &Arc) -> std::result::Result, io::Error> { if quit_flag.load(Ordering::SeqCst) { return Err(io::Error::new(io::ErrorKind::Other, "listener shutdown for quit flag")); } let mut pollers = vec![ libc::pollfd { fd: self.monitor_fd.0, events: libc::POLLIN, revents: 0, }, libc::pollfd { fd: self.fd, events: libc::POLLIN, revents: 0, }, ]; let returned = unsafe { let pollers: &mut [libc::pollfd] = &mut pollers; libc::poll( pollers as *mut _ as *mut libc::pollfd, pollers.len() as _, -1, ) }; if returned == -1 { let err = io::Error::last_os_error(); if err.raw_os_error() == Some(libc::EINTR) { return Err(err); } error!("fatal error in listener_loop:{:?}", err); return Err(err); } else if returned < 1 { return Ok(None) } if pollers[0].revents != 0 || pollers[pollers.len() - 1].revents == 0 { return Ok(None); } if quit_flag.load(Ordering::SeqCst) { return Err(io::Error::new(io::ErrorKind::Other, "listener shutdown for quit flag")); } #[cfg(target_os = "linux")] let fd = match accept4(self.fd, SockFlag::SOCK_CLOEXEC) { Ok(fd) => fd, Err(e) => { error!("failed to accept error {:?}", e); return Err(std::io::Error::from_raw_os_error(e as i32)); } }; // Non Linux platforms do not support accept4 with SOCK_CLOEXEC flag, so instead // use accept and call fcntl separately to set SOCK_CLOEXEC. // Because of this there is chance of the descriptor leak if fork + exec happens in between. #[cfg(target_os = "macos")] let fd = match accept(self.fd) { Ok(fd) => { if let Err(err) = set_fd_close_exec(fd) { error!("fcntl failed after accept: {:?}", err); return Err(io::Error::new(io::ErrorKind::Other, format!("{err:?}"))); }; fd } Err(e) => { error!("failed to accept error {:?}", e); return Err(std::io::Error::from_raw_os_error(e as i32)); } }; Ok(Some(PipeConnection { fd })) } pub fn close(&self) -> Result<()> { close(self.monitor_fd.1).unwrap_or_else(|e| { warn!( "failed to close notify fd: {} with error: {}", self.monitor_fd.1, e ) }); Ok(()) } } pub struct PipeConnection { fd: RawFd, } impl PipeConnection { pub(crate) fn new(fd: RawFd) -> PipeConnection { PipeConnection { fd } } pub(crate) fn id(&self) -> i32 { self.fd } pub fn read(&self, buf: &mut [u8]) -> Result { loop { match recv(self.fd, buf, MsgFlags::empty()) { Ok(l) => return Ok(l), Err(e) if retryable(e) => { // Should retry continue; } Err(e) => { return Err(crate::Error::Nix(e)); } } } } pub fn write(&self, buf: &[u8]) -> Result { loop { match send(self.fd, buf, MsgFlags::empty()) { Ok(l) => return Ok(l), Err(e) if retryable(e) => { // Should retry continue; } Err(e) => { return Err(crate::Error::Nix(e)); } } } } pub fn close(&self) -> Result<()> { match close(self.fd) { Ok(_) => Ok(()), Err(e) => Err(crate::Error::Nix(e)) } } pub fn shutdown(&self) -> Result<()> { match socket::shutdown(self.fd, Shutdown::Read) { Ok(_) => Ok(()), Err(e) => Err(crate::Error::Nix(e)) } } } pub struct ClientConnection { fd: RawFd, socket_pair: (RawFd, RawFd), } impl ClientConnection { pub fn client_connect(sockaddr: &str)-> Result { let fd = unsafe { client_connect(sockaddr)? }; Ok(ClientConnection::new(fd)) } pub(crate) fn new(fd: RawFd) -> ClientConnection { let (recver_fd, close_fd) = socketpair(AddressFamily::Unix, SockType::Stream, None, SOCK_CLOEXEC).unwrap(); // MacOS doesn't support descriptor creation with SOCK_CLOEXEC automically, // so there is a chance of leak if fork + exec happens in between of these calls. #[cfg(target_os = "macos")] { set_fd_close_exec(recver_fd).unwrap(); set_fd_close_exec(close_fd).unwrap(); } ClientConnection { fd, socket_pair: (recver_fd, close_fd) } } pub fn ready(&self) -> std::result::Result, io::Error> { let mut pollers = vec![ libc::pollfd { fd: self.socket_pair.0, events: libc::POLLIN, revents: 0, }, libc::pollfd { fd: self.fd, events: libc::POLLIN, revents: 0, }, ]; let returned = unsafe { let pollers: &mut [libc::pollfd] = &mut pollers; libc::poll( pollers as *mut _ as *mut libc::pollfd, pollers.len() as _, -1, ) }; if returned == -1 { let err = io::Error::last_os_error(); if err.raw_os_error() == Some(libc::EINTR) { return Ok(None) } error!("fatal error in process reaper:{}", err); return Err(err); } else if returned < 1 { return Ok(None) } if pollers[0].revents != 0 { return Err(io::Error::new(io::ErrorKind::Other, "pipe closed")); } if pollers[pollers.len() - 1].revents == 0 { return Ok(None) } Ok(Some(())) } pub fn get_pipe_connection(&self) -> Result { Ok(PipeConnection::new(self.fd)) } pub fn close_receiver(&self) -> Result<()> { match close(self.socket_pair.0) { Ok(_) => Ok(()), Err(e) => Err(crate::Error::Nix(e)) } } pub fn close(&self) -> Result<()> { match close(self.socket_pair.1) { Ok(_) => {}, Err(e) => return Err(crate::Error::Nix(e)) }; match close(self.fd) { Ok(_) => Ok(()), Err(e) => Err(crate::Error::Nix(e)) } } } fn retryable(e: nix::Error) -> bool { e == Error::EINTR || e == Error::EAGAIN } ttrpc-0.8.1/src/sync/sys/windows/mod.rs000064400000000000000000000001101046102023000161560ustar 00000000000000mod net; pub use net::{PipeConnection, PipeListener, ClientConnection}; ttrpc-0.8.1/src/sync/sys/windows/net.rs000064400000000000000000000412751046102023000162060ustar 00000000000000/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ use crate::error::Result; use crate::error::Error; use std::cell::UnsafeCell; use std::ffi::OsStr; use std::fs::OpenOptions; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::{IntoRawHandle}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc}; use std::{io}; use windows_sys::Win32::Foundation::{ CloseHandle, ERROR_IO_PENDING, ERROR_PIPE_CONNECTED, INVALID_HANDLE_VALUE }; use windows_sys::Win32::Storage::FileSystem::{ ReadFile, WriteFile, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, PIPE_ACCESS_DUPLEX }; use windows_sys::Win32::System::IO::{ GetOverlappedResult, OVERLAPPED }; use windows_sys::Win32::System::Pipes::{ CreateNamedPipeW, ConnectNamedPipe,DisconnectNamedPipe, PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, PIPE_REJECT_REMOTE_CLIENTS }; use windows_sys::Win32::System::Threading::{CreateEventW, SetEvent}; const PIPE_BUFFER_SIZE: u32 = 65536; const WAIT_FOR_EVENT: i32 = 1; pub struct PipeListener { first_instance: AtomicBool, shutting_down: AtomicBool, address: String, connection_event: isize, } #[repr(C)] struct Overlapped { inner: UnsafeCell, } impl Overlapped { fn new_with_event(event: isize) -> Overlapped { let mut ol = Overlapped { inner: UnsafeCell::new(unsafe { std::mem::zeroed() }), }; ol.inner.get_mut().hEvent = event; ol } fn as_mut_ptr(&self) -> *mut OVERLAPPED { self.inner.get() } } impl PipeListener { pub(crate) fn new(sockaddr: &str) -> Result { let connection_event = create_event()?; Ok(PipeListener { first_instance: AtomicBool::new(true), shutting_down: AtomicBool::new(false), address: sockaddr.to_string(), connection_event }) } // accept returns: // - Ok(Some(PipeConnection)) if a new connection is established // - Err(io::Error) if there is an error and listener loop should be shutdown pub(crate) fn accept(&self, quit_flag: &Arc) -> std::result::Result, io::Error> { if quit_flag.load(Ordering::SeqCst) { return Err(io::Error::new( io::ErrorKind::Other, "listener shutdown for quit flag", )); } // Create a new pipe instance for every new client let instance = self.new_instance()?; let np = match PipeConnection::new(instance) { Ok(np) => np, Err(e) => { return Err(io::Error::new( io::ErrorKind::Other, format!("failed to create new pipe instance: {:?}", e), )); } }; let ol = Overlapped::new_with_event(self.connection_event); trace!("listening for connection"); let result = unsafe { ConnectNamedPipe(np.named_pipe, ol.as_mut_ptr())}; if result != 0 { if let Some(error) = self.handle_shutdown(&np) { return Err(error); } return Err(io::Error::last_os_error()); } match io::Error::last_os_error() { e if e.raw_os_error() == Some(ERROR_IO_PENDING as i32) => { let mut bytes_transfered = 0; let res = unsafe {GetOverlappedResult(np.named_pipe, ol.as_mut_ptr(), &mut bytes_transfered, WAIT_FOR_EVENT) }; match res { 0 => { return Err(io::Error::last_os_error()); } _ => { if let Some(shutdown_signal) = self.handle_shutdown(&np) { return Err(shutdown_signal); } Ok(Some(np)) } } } e if e.raw_os_error() == Some(ERROR_PIPE_CONNECTED as i32) => { if let Some(error) = self.handle_shutdown(&np) { return Err(error); } Ok(Some(np)) } e => { return Err(io::Error::new( io::ErrorKind::Other, format!("failed to connect pipe: {:?}", e), )); } } } fn handle_shutdown(&self, np: &PipeConnection) -> Option { if self.shutting_down.load(Ordering::SeqCst) { np.close().unwrap_or_else(|err| trace!("Failed to close the pipe {:?}", err)); return Some(io::Error::new( io::ErrorKind::Other, "closing pipe", )); } None } fn new_instance(&self) -> io::Result { let name = OsStr::new(&self.address.as_str()) .encode_wide() .chain(Some(0)) // add NULL termination .collect::>(); let mut open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED ; if self.first_instance.load(Ordering::SeqCst) { open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE; self.first_instance.swap(false, Ordering::SeqCst); } // null for security attributes means the handle cannot be inherited and write access is restricted to system // https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights match unsafe { CreateNamedPipeW(name.as_ptr(), open_mode, PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, PIPE_BUFFER_SIZE, 0, std::ptr::null_mut())} { INVALID_HANDLE_VALUE => { return Err(io::Error::last_os_error()) } h => { return Ok(h) }, }; } pub fn close(&self) -> Result<()> { // release the ConnectNamedPipe thread by signaling the event and clean up event handle self.shutting_down.store(true, Ordering::SeqCst); set_event(self.connection_event)?; close_handle(self.connection_event) } } pub struct PipeConnection { named_pipe: isize, read_event: isize, write_event: isize, } // PipeConnection on Windows is used by both the Server and Client to read and write to the named pipe // The named pipe is created with the overlapped flag enable the simultaneous read and write operations. // This is required since a read and write be issued at the same time on a given named pipe instance. // // An event is created for the read and write operations. When the read or write is issued // it either returns immediately or the thread is suspended until the event is signaled when // the overlapped (async) operation completes and the event is triggered allow the thread to continue. // // Due to the implementation of the sync Server and client there is always only one read and one write // operation in flight at a time so we can reuse the same event. // // For more information on overlapped and events: https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult#remarks // "It is safer to use an event object because of the confusion that can occur when multiple simultaneous overlapped operations are performed on the same file, named pipe, or communications device." // "In this situation, there is no way to know which operation caused the object's state to be signaled." impl PipeConnection { pub(crate) fn new(h: isize) -> Result { trace!("creating events for thread {:?} on pipe instance {}", std::thread::current().id(), h as i32); let read_event = create_event()?; let write_event = create_event()?; Ok(PipeConnection { named_pipe: h, read_event: read_event, write_event: write_event, }) } pub(crate) fn id(&self) -> i32 { self.named_pipe as i32 } pub fn read(&self, buf: &mut [u8]) -> Result { trace!("starting read for thread {:?} on pipe instance {}", std::thread::current().id(), self.named_pipe as i32); let ol = Overlapped::new_with_event(self.read_event); let len = std::cmp::min(buf.len(), u32::MAX as usize) as u32; let mut bytes_read= 0; let result = unsafe { ReadFile(self.named_pipe, buf.as_mut_ptr() as *mut _, len, &mut bytes_read,ol.as_mut_ptr()) }; if result > 0 && bytes_read > 0 { // Got result no need to wait for pending read to complete return Ok(bytes_read as usize) } // wait for pending operation to complete (thread will be suspended until event is signaled) match io::Error::last_os_error() { ref e if e.raw_os_error() == Some(ERROR_IO_PENDING as i32) => { let mut bytes_transfered = 0; let res = unsafe {GetOverlappedResult(self.named_pipe, ol.as_mut_ptr(), &mut bytes_transfered, WAIT_FOR_EVENT) }; match res { 0 => { return Err(handle_windows_error(io::Error::last_os_error())) } _ => { return Ok(bytes_transfered as usize) } } } ref e => { return Err(Error::Others(format!("failed to read from pipe: {:?}", e))) } } } pub fn write(&self, buf: &[u8]) -> Result { trace!("starting write for thread {:?} on pipe instance {}", std::thread::current().id(), self.named_pipe as i32); let ol = Overlapped::new_with_event(self.write_event); let mut bytes_written = 0; let len = std::cmp::min(buf.len(), u32::MAX as usize) as u32; let result = unsafe { WriteFile(self.named_pipe, buf.as_ptr() as *const _,len, &mut bytes_written, ol.as_mut_ptr())}; if result > 0 && bytes_written > 0 { // No need to wait for pending write to complete return Ok(bytes_written as usize) } // wait for pending operation to complete (thread will be suspended until event is signaled) match io::Error::last_os_error() { ref e if e.raw_os_error() == Some(ERROR_IO_PENDING as i32) => { let mut bytes_transfered = 0; let res = unsafe {GetOverlappedResult(self.named_pipe, ol.as_mut_ptr(), &mut bytes_transfered, WAIT_FOR_EVENT) }; match res { 0 => { return Err(handle_windows_error(io::Error::last_os_error())) } _ => { return Ok(bytes_transfered as usize) } } } ref e => { return Err(Error::Others(format!("failed to write to pipe: {:?}", e))) } } } pub fn close(&self) -> Result<()> { close_handle(self.named_pipe)?; close_handle(self.read_event)?; close_handle(self.write_event) } pub fn shutdown(&self) -> Result<()> { let result = unsafe { DisconnectNamedPipe(self.named_pipe) }; match result { 0 => Err(handle_windows_error(io::Error::last_os_error())), _ => Ok(()), } } } pub struct ClientConnection { address: String } fn close_handle(handle: isize) -> Result<()> { let result = unsafe { CloseHandle(handle) }; match result { 0 => Err(handle_windows_error(io::Error::last_os_error())), _ => Ok(()), } } fn create_event() -> Result { let result = unsafe { CreateEventW(std::ptr::null_mut(), 0, 1, std::ptr::null_mut()) }; match result { 0 => Err(handle_windows_error(io::Error::last_os_error())), _ => Ok(result), } } fn set_event(event: isize) -> Result<()> { let result = unsafe { SetEvent(event) }; match result { 0 => Err(handle_windows_error(io::Error::last_os_error())), _ => Ok(()), } } impl ClientConnection { pub fn client_connect(sockaddr: &str) -> Result { Ok(ClientConnection::new(sockaddr)) } pub(crate) fn new(sockaddr: &str) -> ClientConnection { ClientConnection { address: sockaddr.to_string() } } pub fn ready(&self) -> std::result::Result, io::Error> { // Windows is a "completion" based system so "readiness" isn't really applicable Ok(Some(())) } pub fn get_pipe_connection(&self) -> Result { let mut opts = OpenOptions::new(); opts.read(true) .write(true) .custom_flags(FILE_FLAG_OVERLAPPED); match opts.open(self.address.as_str()) { Ok(file) => { return PipeConnection::new(file.into_raw_handle() as isize) } Err(e) => { return Err(handle_windows_error(e)) } } } pub fn close_receiver(&self) -> Result<()> { // close the pipe from the pipe connection Ok(()) } pub fn close(&self) -> Result<()> { // close the pipe from the pipe connection Ok(()) } } fn handle_windows_error(e: io::Error) -> Error { if let Some(raw_os_err) = e.raw_os_error() { Error::Windows(raw_os_err) } else { Error::Others(e.to_string()) } } #[cfg(test)] mod test { use super::*; use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; #[test] fn test_pipe_connection() { let client = ClientConnection::new("non_existent_pipe"); match client.get_pipe_connection() { Ok(_) => { assert!(false, "should not be able to get a connection to a non existent pipe"); } Err(e) => { assert_eq!(e, Error::Windows(ERROR_FILE_NOT_FOUND as i32)); } } } #[test] fn should_accept_new_client() { let address = r"\\.\pipe\ttrpc-test-accept"; let listener = Arc::new(PipeListener::new(address).unwrap()); let listener_server = listener.clone(); let thread = std::thread::spawn(move || { let quit_flag = Arc::new(AtomicBool::new(false)); match listener_server.accept(&quit_flag) { Ok(Some(_)) => { // pipe is working } Ok(None) => { assert!(false, "should get a working pipe") } Err(e) => { assert!(false, "should not get error {}", e.to_string()) } } }); wait_socket_working(address, 10, 5).unwrap(); thread.join().unwrap(); } #[test] fn close_should_cancel_accept() { let listener = Arc::new(PipeListener::new(r"\\.\pipe\ttrpc-test-close").unwrap()); let listener_server = listener.clone(); let thread = std::thread::spawn(move || { let quit_flag = Arc::new(AtomicBool::new(false)); match listener_server.accept(&quit_flag) { Ok(_) => { assert!(false, "should not get pipe on close") } Err(e) => { assert_eq!(e.to_string(), "closing pipe") } } }); // sleep for a moment to allow the pipe to start initialize and be ready to accept new connection. // this simulates scenario where the thread is asleep and awaiting a connection std::thread::sleep(std::time::Duration::from_millis(500)); listener.close().unwrap(); thread.join().unwrap(); } fn wait_socket_working(address: &str, interval_in_ms: u64, count: u32) -> Result<()> { for _i in 0..count { let client = match ClientConnection::client_connect(address) { Ok(c) => { c } Err(_) => { std::thread::sleep(std::time::Duration::from_millis(interval_in_ms)); continue; } }; match client.get_pipe_connection() { Ok(_) => { return Ok(()); } Err(_) => { std::thread::sleep(std::time::Duration::from_millis(interval_in_ms)); } } } Err(Error::Others("timed out".to_string())) } } ttrpc-0.8.1/src/sync/utils.rs000064400000000000000000000077701046102023000142520ustar 00000000000000// Copyright (c) 2020 Ant Financial // // SPDX-License-Identifier: Apache-2.0 // use crate::error::{Error, Result}; use crate::proto::{ check_oversize, Codec, MessageHeader, Request, Response, MESSAGE_TYPE_RESPONSE, }; use std::collections::HashMap; /// Response message through a channel. /// Eventually the message will sent to Client. pub fn response_to_channel( stream_id: u32, res: Response, tx: std::sync::mpsc::Sender<(MessageHeader, Vec)>, ) -> Result<()> { let mut buf = res.encode().map_err(err_to_others_err!(e, ""))?; if let Err(e) = check_oversize(buf.len(), true) { let resp: Response = e.into(); buf = resp.encode().map_err(err_to_others_err!(e, ""))?; }; let mh = MessageHeader { length: buf.len() as u32, stream_id, type_: MESSAGE_TYPE_RESPONSE, flags: 0, }; tx.send((mh, buf)).map_err(err_to_others_err!(e, ""))?; Ok(()) } pub fn response_error_to_channel( stream_id: u32, e: Error, tx: std::sync::mpsc::Sender<(MessageHeader, Vec)>, ) -> Result<()> { response_to_channel(stream_id, e.into(), tx) } /// Handle request in sync mode. #[macro_export] macro_rules! request_handler { ($class: ident, $ctx: ident, $req: ident, $server: ident, $req_type: ident, $req_fn: ident) => { let mut s = CodedInputStream::from_bytes(&$req.payload); let mut req = super::$server::$req_type::new(); req.merge_from(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; let mut res = ::ttrpc::Response::new(); match $class.service.$req_fn(&$ctx, req) { Ok(rep) => { res.set_status(::ttrpc::get_status(::ttrpc::Code::OK, "".to_string())); res.payload.reserve(rep.compute_size() as usize); let mut s = protobuf::CodedOutputStream::vec(&mut res.payload); rep.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; } Err(x) => match x { ::ttrpc::Error::RpcStatus(s) => { res.set_status(s); } _ => { res.set_status(::ttrpc::get_status( ::ttrpc::Code::UNKNOWN, format!("{:?}", x), )); } }, } ::ttrpc::response_to_channel($ctx.mh.stream_id, res, $ctx.res_tx)? }; } /// Send request through sync client. #[macro_export] macro_rules! client_request { ($self: ident, $ctx: ident, $req: ident, $server: expr, $method: expr, $cres: ident) => { let mut creq = ::ttrpc::Request::new(); creq.set_service($server.to_string()); creq.set_method($method.to_string()); creq.set_timeout_nano($ctx.timeout_nano); let md = ::ttrpc::context::to_pb($ctx.metadata); creq.set_metadata(md); creq.payload.reserve($req.compute_size() as usize); let mut s = CodedOutputStream::vec(&mut creq.payload); $req.write_to(&mut s) .map_err(::ttrpc::err_to_others!(e, ""))?; s.flush().map_err(::ttrpc::err_to_others!(e, ""))?; drop(s); let res = $self.client.request(creq)?; let mut s = CodedInputStream::from_bytes(&res.payload); $cres .merge_from(&mut s) .map_err(::ttrpc::err_to_others!(e, "Unpack get error "))?; }; } /// The context of ttrpc (sync). #[derive(Debug)] pub struct TtrpcContext { #[cfg(unix)] pub fd: std::os::unix::io::RawFd, #[cfg(windows)] pub fd: i32, pub cancel_rx: crossbeam::channel::Receiver<()>, pub mh: MessageHeader, pub res_tx: std::sync::mpsc::Sender<(MessageHeader, Vec)>, pub metadata: HashMap>, pub timeout_nano: i64, } /// Trait that implements handler which is a proxy to the desired method (sync). pub trait MethodHandler { fn handler(&self, ctx: TtrpcContext, req: Request) -> Result<()>; } ttrpc-0.8.1/src/ttrpc.proto000064400000000000000000000222251046102023000140010ustar 00000000000000// Copyright (c) 2019 Ant Financial // // 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. syntax = "proto3"; package grpc; message Request { string service = 1; string method = 2; bytes payload = 3; int64 timeout_nano = 4; repeated KeyValue metadata = 5; } message KeyValue { string key = 1; string value = 2; } // Get from github.com/gogo/protobuf/protobuf/google/protobuf/any.proto message Any { // A URL/resource name that uniquely identifies the type of the serialized // protocol buffer message. The last segment of the URL's path must represent // the fully qualified name of the type (as in // `path/google.protobuf.Duration`). The name should be in a canonical form // (e.g., leading "." is not accepted). // // In practice, teams usually precompile into the binary all types that they // expect it to use in the context of Any. However, for URLs which use the // scheme `http`, `https`, or no scheme, one can optionally set up a type // server that maps type URLs to message definitions as follows: // // * If no scheme is provided, `https` is assumed. // * An HTTP GET on the URL must yield a [google.protobuf.Type][] // value in binary format, or produce an error. // * Applications are allowed to cache lookup results based on the // URL, or have them precompiled into a binary to avoid any // lookup. Therefore, binary compatibility needs to be preserved // on changes to types. (Use versioned type names to manage // breaking changes.) // // Note: this functionality is not currently available in the official // protobuf release, and it is not used for type URLs beginning with // type.googleapis.com. // // Schemes other than `http`, `https` (or the empty scheme) might be // used with implementation specific semantics. // string type_url = 1; // Must be a valid serialized protocol buffer of the above specified type. bytes value = 2; } // Get from github.com/gogo/googleapis/google/rpc/code.proto // The canonical error codes for Google APIs. // // // Sometimes multiple error codes may apply. Services should return // the most specific error code that applies. For example, prefer // `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. // Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. enum Code { // Not an error; returned on success // // HTTP Mapping: 200 OK OK = 0; // The operation was cancelled, typically by the caller. // // HTTP Mapping: 499 Client Closed Request CANCELLED = 1; // Unknown error. For example, this error may be returned when // 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. // // HTTP Mapping: 500 Internal Server Error UNKNOWN = 2; // The client specified an invalid argument. Note that this differs // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments // that are problematic regardless of the state of the system // (e.g., a malformed file name). // // HTTP Mapping: 400 Bad Request INVALID_ARGUMENT = 3; // The deadline expired before the operation could complete. 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. // // HTTP Mapping: 504 Gateway Timeout DEADLINE_EXCEEDED = 4; // Some requested entity (e.g., file or directory) was not found. // // Note to server developers: if a request is denied for an entire class // of users, such as gradual feature rollout or undocumented whitelist, // `NOT_FOUND` may be used. If a request is denied for some users within // a class of users, such as user-based access control, `PERMISSION_DENIED` // must be used. // // HTTP Mapping: 404 Not Found NOT_FOUND = 5; // The entity that a client attempted to create (e.g., file or directory) // already exists. // // HTTP Mapping: 409 Conflict ALREADY_EXISTS = 6; // The caller does not have permission to execute the specified // operation. `PERMISSION_DENIED` must not be used for rejections // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` // instead for those errors). `PERMISSION_DENIED` must not be // used if the caller can not be identified (use `UNAUTHENTICATED` // instead for those errors). This error code does not imply the // request is valid or the requested entity exists or satisfies // other pre-conditions. // // HTTP Mapping: 403 Forbidden PERMISSION_DENIED = 7; // The request does not have valid authentication credentials for the // operation. // // HTTP Mapping: 401 Unauthorized UNAUTHENTICATED = 16; // Some resource has been exhausted, perhaps a per-user quota, or // perhaps the entire file system is out of space. // // HTTP Mapping: 429 Too Many Requests RESOURCE_EXHAUSTED = 8; // The operation was rejected because the system is not in a state // required for the operation's execution. For example, the directory // to be deleted is non-empty, an rmdir operation is applied to // a non-directory, etc. // // Service implementors can use the following guidelines to decide // between `FAILED_PRECONDITION`, `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., when a client-specified test-and-set fails, indicating the // client should restart a read-modify-write sequence). // (c) Use `FAILED_PRECONDITION` 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, `FAILED_PRECONDITION` // should be returned since the client should not retry unless // the files are deleted from the directory. // // HTTP Mapping: 400 Bad Request FAILED_PRECONDITION = 9; // The operation was aborted, typically due to a concurrency issue such as // a sequencer check failure or transaction abort. // // See the guidelines above for deciding between `FAILED_PRECONDITION`, // `ABORTED`, and `UNAVAILABLE`. // // HTTP Mapping: 409 Conflict ABORTED = 10; // The operation was attempted past the valid range. E.g., seeking or // reading past end-of-file. // // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may // be fixed if the system state changes. For example, a 32-bit file // system will generate `INVALID_ARGUMENT` if asked to read at an // offset that is not in the range [0,2^32-1], but it will generate // `OUT_OF_RANGE` if asked to read from an offset past the current // file size. // // There is a fair bit of overlap between `FAILED_PRECONDITION` and // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific // error) when it applies so that callers who are iterating through // a space can easily look for an `OUT_OF_RANGE` error to detect when // they are done. // // HTTP Mapping: 400 Bad Request OUT_OF_RANGE = 11; // The operation is not implemented or is not supported/enabled in this // service. // // HTTP Mapping: 501 Not Implemented UNIMPLEMENTED = 12; // Internal errors. This means that some invariants expected by the // underlying system have been broken. This error code is reserved // for serious errors. // // HTTP Mapping: 500 Internal Server Error INTERNAL = 13; // The service is currently unavailable. This is most likely a // transient condition, which can be corrected by retrying with // a backoff. // // See the guidelines above for deciding between `FAILED_PRECONDITION`, // `ABORTED`, and `UNAVAILABLE`. // // HTTP Mapping: 503 Service Unavailable UNAVAILABLE = 14; // Unrecoverable data loss or corruption. // // HTTP Mapping: 500 Internal Server Error DATA_LOSS = 15; } // Get from github.com/gogo/googleapis/google/rpc/status.proto message Status { // The status code, which should be an enum value of // [google.rpc.Code][google.rpc.Code]. Code code = 1; // A developer-facing error message, which should be in English. Any // user-facing error message should be localized and sent in the // [google.rpc.Status.details][google.rpc.Status.details] field, or localized // by the client. string message = 2; // A list of messages that carry the error details. There is a common set of // message types for APIs to use. repeated Any details = 3; } message Response { Status status = 1; bytes payload = 2; } ttrpc-0.8.1/tests/sync-test.rs000064400000000000000000000033671046102023000144400ustar 00000000000000use std::{ io::{BufRead, BufReader}, process::Command, time::Duration, }; #[test] fn run_sync_example() -> Result<(), Box> { // start the server and give it a moment to start. let mut server = run_example("server").spawn().unwrap(); std::thread::sleep(Duration::from_secs(2)); let mut client = run_example("client").spawn().unwrap(); let mut client_succeeded = false; let start = std::time::Instant::now(); let timeout = Duration::from_secs(600); loop { if start.elapsed() > timeout { println!("Running the client timed out. output:"); client.kill().unwrap_or_else(|e| { println!("This may occur on Windows if the process has exited: {e}"); }); let output = client.stdout.unwrap(); BufReader::new(output).lines().for_each(|line| { println!("{}", line.unwrap()); }); break; } match client.try_wait() { Ok(Some(status)) => { client_succeeded = status.success(); break; } Ok(None) => { // still running continue; } Err(e) => { println!("Error: {e}"); break; } } } // be sure to clean up the server, the client should have run to completion server.kill()?; assert!(client_succeeded); Ok(()) } fn run_example(example: &str) -> Command { let mut cmd = Command::new("cargo"); cmd.arg("run") .arg("--example") .arg(example) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .current_dir("example"); cmd }