diff --git a/.github/workflows/ci-preemptive.sh b/.github/workflows/ci-preemptive.sh new file mode 100755 index 00000000..1a244eaf --- /dev/null +++ b/.github/workflows/ci-preemptive.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh + +set -ex + +CARGO=cargo +if [ "${CROSS}" = "1" ]; then + export CARGO_NET_RETRY=5 + export CARGO_NET_TIMEOUT=10 + + cargo install cross + CARGO=cross +fi + +# If a test crashes, we want to know which one it was. +export RUST_TEST_THREADS=1 +export RUST_BACKTRACE=1 + +# test open-coroutine-core mod +cd "${PROJECT_DIR}"/core +"${CARGO}" test --target "${TARGET}" --features preemptive +"${CARGO}" test --target "${TARGET}" --features preemptive --release + +# test open-coroutine +cd "${PROJECT_DIR}"/open-coroutine +"${CARGO}" test --target "${TARGET}" --features preemptive +"${CARGO}" test --target "${TARGET}" --features preemptive --release + +# test io_uring +if [ "${TARGET}" = "x86_64-unknown-linux-gnu" ]; then + cd "${PROJECT_DIR}"/core + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring,preemptive + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring,preemptive --release + cd "${PROJECT_DIR}"/open-coroutine + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring,preemptive + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring,preemptive --release +fi diff --git a/.github/workflows/ci.sh b/.github/workflows/ci.sh new file mode 100755 index 00000000..47b5a23c --- /dev/null +++ b/.github/workflows/ci.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env sh + +set -ex + +CARGO=cargo +if [ "${CROSS}" = "1" ]; then + export CARGO_NET_RETRY=5 + export CARGO_NET_TIMEOUT=10 + + cargo install cross + CARGO=cross +fi + +# If a test crashes, we want to know which one it was. +export RUST_TEST_THREADS=1 +export RUST_BACKTRACE=1 + +# test open-coroutine-core mod +cd "${PROJECT_DIR}"/core +"${CARGO}" test --target "${TARGET}" +"${CARGO}" test --target "${TARGET}" --release + +# test open-coroutine +cd "${PROJECT_DIR}"/open-coroutine +"${CARGO}" test --target "${TARGET}" +"${CARGO}" test --target "${TARGET}" --release + +# test io_uring +if [ "${TARGET}" = "x86_64-unknown-linux-gnu" ]; then + cd "${PROJECT_DIR}"/core + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring --release + cd "${PROJECT_DIR}"/open-coroutine + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring + "${CARGO}" test --target "${TARGET}" --no-default-features --features io_uring --release +fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 175b9adb..7791551c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,256 +7,103 @@ on: pull_request: paths-ignore: - '**.md' - workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: - lints: - name: Run cargo fmt and cargo clippy - runs-on: ubuntu-latest + test: + runs-on: ${{ matrix.os }} steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install toolchain - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 with: - profile: minimal - toolchain: stable + toolchain: ${{ matrix.target == 'i686-pc-windows-gnu' && format('{0}-i686-pc-windows-gnu', matrix.channel) || matrix.channel }} + target: ${{ matrix.target }} override: true - components: rustfmt, clippy - - name: cargo fmt --check - uses: actions-rs/cargo@v1 + components: rustfmt + - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - - name: Run cargo clippy - uses: actions-rs/cargo@v1 + - name: Run cargo deny + if: ${{ contains(matrix.os, 'ubuntu') }} + uses: EmbarkStudios/cargo-deny-action@v2 with: - command: clippy - args: -- -D warnings + log-level: warn + command: check + arguments: --all-features + - name: Run tests + env: + CHANNEL: ${{ matrix.channel }} + CROSS: ${{ !startsWith(matrix.target, 'x86_64') && contains(matrix.target, 'linux') && '1' || '0' }} + TARGET: ${{ matrix.target }} + OS: ${{ matrix.os }} + PROJECT_DIR: ${{ github.workspace }} + run: sh .github/workflows/ci.sh + - name: Run preemptive tests + if: always() + env: + CHANNEL: ${{ matrix.channel }} + CROSS: ${{ !startsWith(matrix.target, 'x86_64') && contains(matrix.target, 'linux') && '1' || '0' }} + TARGET: ${{ matrix.target }} + OS: ${{ matrix.os }} + PROJECT_DIR: ${{ github.workspace }} + run: sh .github/workflows/ci-preemptive.sh - linux: - name: Test ${{ matrix.rust }} on ubuntu-latest - runs-on: ubuntu-latest strategy: fail-fast: false matrix: - rust: - - stable - - nightly - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Run cargo clean - run: | - cd ${{ github.workspace }} - /home/runner/.cargo/bin/cargo clean - - name: Run cargo release test compile - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all --no-run - - name: Run cargo release test - run: sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo test --release --all" - - name: Run cargo release preemptive example - if: always() - run: | - cd ${{ github.workspace }}/open-coroutine-core - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example preemptive --release --features preemptive-schedule" - - name: Run cargo release test io_uring - if: always() - run: | - cd ${{ github.workspace }}/open-coroutine-iouring - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo test --release" - - name: Run cargo release sleep not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example sleep_not_co --release" - - name: Run cargo release sleep coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example sleep_co --release" - - name: Run cargo release socket not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_not_co --release" - - name: Run cargo release socket coroutine server example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co_server --release" - - name: Run cargo release socket coroutine client example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co_client --release" - - name: Run cargo release socket coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co --release" - - name: Run cargo release socket not coroutine with io_uring example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_not_co --release --features io_uring" - - name: Run cargo release socket coroutine server with io_uring example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co_server --release --features io_uring" - - name: Run cargo release socket coroutine client with io_uring example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co_client --release --features io_uring" - - name: Run cargo release socket coroutine with io_uring example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /home/runner/.cargo/bin/cargo run --example socket_co --release --features io_uring" + target: [ + x86_64-unknown-linux-gnu, + i686-unknown-linux-gnu, + aarch64-unknown-linux-gnu, + armv7-unknown-linux-gnueabihf, + riscv64gc-unknown-linux-gnu, + thumbv7neon-unknown-linux-gnueabihf, +# mips64-unknown-linux-muslabi64, +# loongarch64-unknown-linux-gnu, +# s390x-unknown-linux-gnu, - macos: - name: Test ${{ matrix.rust }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - rust: - - stable - - nightly - os: - - macos-latest - - macos-14 - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Run cargo clean - run: | - cd ${{ github.workspace }} - /Users/runner/.cargo/bin/cargo clean - - name: Run cargo release test compile - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all --no-run - - name: Run cargo release test - run: sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo test --release --all" - - name: Run cargo release preemptive example - if: always() - run: | - cd ${{ github.workspace }}/open-coroutine-core - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example preemptive --release --features preemptive-schedule" - - name: Run cargo release sleep not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example sleep_not_co --release" - - name: Run cargo release sleep coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example sleep_co --release" - - name: Run cargo release socket not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example socket_not_co --release" - - name: Run cargo release socket coroutine server example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example socket_co_server --release" - - name: Run cargo release socket coroutine client example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example socket_co_client --release" - - name: Run cargo release socket coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - sudo bash -c "sudo -u runner RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 /Users/runner/.cargo/bin/cargo run --example socket_co --release" + x86_64-apple-darwin, + aarch64-apple-darwin, - windows: - name: Test ${{ matrix.rust }} on windows-latest - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - rust: - # stable is not supported due to static-detour in retour crate -# - stable-x86_64-pc-windows-gnu - - nightly-x86_64-pc-windows-gnu - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Run cargo clean - run: | - cd ${{ github.workspace }} - C://Users//runneradmin//.cargo//bin//cargo.exe clean - - name: Run cargo release test compile - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all --no-run - - name: Run cargo release test - run: bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe test --release --all" - - name: Run cargo release preemptive example - if: always() - run: | - cd ${{ github.workspace }}/open-coroutine-core - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example preemptive --release --features preemptive-schedule" - - name: Run cargo release sleep not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example sleep_not_co --release" - - name: Run cargo release sleep coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example sleep_co --release" - - name: Run cargo release socket not coroutine example - if: always() - run: | - cd ${{ github.workspace }}/examples - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example socket_not_co --release" - - name: Run cargo release socket coroutine server example - if: always() - run: | - cd ${{ github.workspace }}/examples - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example socket_co_server --release" - - name: Run cargo release socket coroutine client example - if: always() - run: | - cd ${{ github.workspace }}/examples - bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example socket_co_client --release" -# - name: Run cargo release socket coroutine example -# if: always() -# run: | -# cd ${{ github.workspace }}/examples -# bash -c "RUSTUP_TOOLCHAIN=${{ matrix.rust }} RUST_BACKTRACE=1 C://Users//runneradmin//.cargo//bin//cargo.exe run --example socket_co --release" + x86_64-pc-windows-gnu, + i686-pc-windows-gnu, + x86_64-pc-windows-msvc, + i686-pc-windows-msvc, + ] + channel: [ 1.81.0, nightly-2024-08-02 ] + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: i686-unknown-linux-gnu + os: ubuntu-latest + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + - target: armv7-unknown-linux-gnueabihf + os: ubuntu-latest + - target: riscv64gc-unknown-linux-gnu + os: ubuntu-latest + - target: thumbv7neon-unknown-linux-gnueabihf + os: ubuntu-latest +# - target: mips64-unknown-linux-muslabi64 +# os: ubuntu-latest +# - target: loongarch64-unknown-linux-gnu +# os: ubuntu-latest +# - target: s390x-unknown-linux-gnu +# os: ubuntu-latest + + - target: x86_64-apple-darwin + os: macos-latest + - target: aarch64-apple-darwin + os: macos-14 + + - target: x86_64-pc-windows-gnu + os: windows-latest + - target: i686-pc-windows-gnu + os: windows-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + - target: i686-pc-windows-msvc + os: windows-latest diff --git a/.github/workflows/pr-audit.yml b/.github/workflows/pr-audit.yml index c6ff721e..a02666dc 100644 --- a/.github/workflows/pr-audit.yml +++ b/.github/workflows/pr-audit.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, 'ci skip')" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install cargo-audit uses: actions-rs/cargo@v1 diff --git a/.gitignore b/.gitignore index b8aed76b..df5c3508 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,3 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# idea ignore -.idea/ -*.ipr -*.iml -*.iws - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -*.lock - -# These are backup files generated by rustfmt -**/*.rs.bk +/target +.idea +*.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9f46fcac..d4efe6ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,57 @@ [workspace] resolver = "2" members = [ - "open-coroutine-timer", - "open-coroutine-queue", - "open-coroutine-iouring", - "open-coroutine-core", - "open-coroutine-hooks", - "open-coroutine-macros", - "open-coroutine", - "examples" + "core", + "hook", + "macros", + "open-coroutine" ] + +[workspace.package] +version = "0.6.10" +edition = "2021" +authors = ["zhangzicheng@apache.org"] +repository = "https://github.com/acl-dev/open-coroutine" +license = "Apache-2.0" +readme = "README.md" + +[workspace.dependencies] +open-coroutine-core = { path = "core", version = "0.6.0" } +open-coroutine-hook = { path = "hook", version = "0.6.0" } +open-coroutine-macros = { path = "macros", version = "0.6.0" } + +tracing = { version = "0.1", default-features = false } +tracing-subscriber = { version = "0.3", default-features = false } +tracing-appender = { version = "0.2", default-features = false } +cargo_metadata = { version = "0.18", default-features = false } +mio = { version = "1.0", default-features = false } + +cfg-if = "1.0.0" +polling = "2.8.0" + +libc = "0.2" +rand = "0.8" +st3 = "0.4" +crossbeam-deque = "0.8" +time = "0.3" +corosensei = "0.2" +core_affinity = "0.8" +crossbeam-utils = "0.8" +nix = "0.29" +io-uring = "0.7" +windows-sys = "0.59" +anyhow = "1.0" +slab = "0.4" +backtrace = "0.3" +minhook = "0.6" +psm = "0.1" + +once_cell = "1" +dashmap = "6" +num_cpus = "1" +uuid = "1" +derivative = "2" +tempfile = "3" +cc = "1" +syn = "2" +quote = "1" diff --git a/README.md b/README.md index 8c0ae5ed..3afb5f09 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,6 @@ nanosleep hooked ### todo -- [ ] support scalable stack - [ ] support and compatibility for AF_XDP socket - [ ] hook other syscall maybe interrupt by signal
@@ -204,6 +203,10 @@ nanosleep hooked
- [ ] support `#[open_coroutine::join]` macro to wait coroutines +### 0.6.x + +- [x] support scalable stack + ### 0.5.x - [x] refactor syscall state, distinguish between state and innerState diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 794dd45f..00000000 --- a/TODO.md +++ /dev/null @@ -1,3 +0,0 @@ -EventLoops does not perform load balancing - -refactor CI \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 00000000..39e20026 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,93 @@ +[package] +name = "open-coroutine-core" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "The open-coroutine is a simple, efficient and generic coroutine library." +repository.workspace = true +keywords = ["runtime", "coroutine", "hook", "preempt", "work-steal"] +categories = ["concurrency", "asynchronous", "os", "network-programming", "wasm"] +license.workspace = true +readme.workspace = true + +[dependencies] +cfg-if.workspace = true +once_cell.workspace = true +dashmap.workspace = true +num_cpus.workspace = true +rand.workspace = true +st3.workspace = true +crossbeam-deque.workspace = true +tracing = { workspace = true, default-features = false, optional = true } +tracing-subscriber = { workspace = true, features = [ + "fmt", + "local-time" +], default-features = false, optional = true } +time = { workspace = true, optional = true } +corosensei = { workspace = true, optional = true } +uuid = { workspace = true, features = [ + "v4", + "fast-rng", +], optional = true } +derivative = { workspace = true, optional = true } +core_affinity = { workspace = true, optional = true } +crossbeam-utils = { workspace = true, optional = true } +psm.workspace = true + +[target.'cfg(unix)'.dependencies] +libc.workspace = true +nix = { workspace = true, features = ["signal"] } +mio = { workspace = true, features = [ + "net", + "os-poll", + "os-ext", +], default-features = false, optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +io-uring = { workspace = true, optional = true } + +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_System_IO", + "Win32_Foundation", + "Win32_System_Kernel", + "Win32_System_Threading", + "Win32_Networking_WinSock", + "Win32_System_SystemInformation", + "Win32_System_Diagnostics_Debug", +] } +polling = { workspace = true, optional = true } + +[build-dependencies] +cfg-if.workspace = true + +[target.'cfg(target_os = "linux")'.build-dependencies] +cc.workspace = true + +[dev-dependencies] +anyhow.workspace = true +slab.workspace = true +backtrace.workspace = true + +[features] +# Print some help log. +# Enable for default. +log = ["tracing", "tracing-subscriber", "time"] + +# low-level raw coroutine +korosensei = ["corosensei", "uuid", "nix/pthread", "derivative"] + +# Provide preemptive scheduling implementation. +# Enable for default. +preemptive = ["korosensei"] + +# Provide net API abstraction and implementation. +net = ["korosensei", "polling", "mio", "crossbeam-utils", "core_affinity"] + +# Provide io_uring adaptation, this feature only works in linux. +io_uring = ["net", "io-uring"] + +# Provide syscall implementation. +syscall = ["net"] + +default = ["log", "syscall"] \ No newline at end of file diff --git a/open-coroutine-iouring/build.rs b/core/build.rs similarity index 53% rename from open-coroutine-iouring/build.rs rename to core/build.rs index cda86a94..84c5d843 100644 --- a/open-coroutine-iouring/build.rs +++ b/core/build.rs @@ -2,12 +2,8 @@ fn main() { cfg_if::cfg_if! { if #[cfg(target_os = "linux")] { cc::Build::new() - .cpp(true) .warnings(true) - .flag("-Wall") - .flag("-std=c++11") - .flag("-c") - .file("cpp_src/version.cpp") + .file("c_src/version.c") .compile("version"); } } diff --git a/open-coroutine-iouring/cpp_src/version.cpp b/core/c_src/version.c similarity index 69% rename from open-coroutine-iouring/cpp_src/version.cpp rename to core/c_src/version.c index 6e60b02c..19817801 100644 --- a/open-coroutine-iouring/cpp_src/version.cpp +++ b/core/c_src/version.c @@ -1,4 +1,4 @@ -#include "version.h" +#include int linux_version_code() { return LINUX_VERSION_CODE; diff --git a/open-coroutine-core/src/pool/creator.rs b/core/src/co_pool/creator.rs similarity index 78% rename from open-coroutine-core/src/pool/creator.rs rename to core/src/co_pool/creator.rs index e35ec3a3..544107b5 100644 --- a/open-coroutine-core/src/pool/creator.rs +++ b/core/src/co_pool/creator.rs @@ -1,18 +1,18 @@ -use crate::common::{Current, Pool}; -use crate::constants::CoroutineState; +use crate::co_pool::CoroutinePool; +use crate::common::constants::CoroutineState; use crate::coroutine::listener::Listener; -use crate::pool::CoroutinePool; -use crate::scheduler::{SchedulableCoroutine, SchedulableCoroutineState, SchedulableListener}; +use crate::coroutine::local::CoroutineLocal; +use crate::scheduler::SchedulableCoroutineState; use std::sync::atomic::Ordering; #[repr(C)] #[derive(Debug, Default)] pub(crate) struct CoroutineCreator {} -impl Listener<(), (), Option> for CoroutineCreator { +impl Listener<(), Option> for CoroutineCreator { fn on_state_changed( &self, - _: &SchedulableCoroutine, + _: &CoroutineLocal, _: SchedulableCoroutineState, new_state: SchedulableCoroutineState, ) { @@ -41,5 +41,3 @@ impl Listener<(), (), Option> for CoroutineCreator { } } } - -impl SchedulableListener for CoroutineCreator {} diff --git a/core/src/co_pool/mod.rs b/core/src/co_pool/mod.rs new file mode 100644 index 00000000..716ef662 --- /dev/null +++ b/core/src/co_pool/mod.rs @@ -0,0 +1,440 @@ +use crate::co_pool::creator::CoroutineCreator; +use crate::co_pool::task::Task; +use crate::common::beans::BeanFactory; +use crate::common::constants::PoolState; +use crate::common::work_steal::{LocalQueue, WorkStealQueue}; +use crate::common::{get_timeout_time, now, CondvarBlocker}; +use crate::coroutine::suspender::Suspender; +use crate::scheduler::{SchedulableCoroutine, Scheduler}; +use crate::{impl_current_for, impl_display_by_debug, impl_for_named, trace}; +use dashmap::DashMap; +use std::cell::Cell; +use std::io::{Error, ErrorKind}; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +/// Task abstraction and impl. +pub mod task; + +/// Coroutine pool state abstraction and impl. +mod state; + +/// Creator for coroutine pool. +mod creator; + +/// The coroutine pool impls. +#[repr(C)] +#[derive(Debug)] +pub struct CoroutinePool<'p> { + //协程池状态 + state: Cell, + //任务队列 + task_queue: LocalQueue<'p, Task<'p>>, + //工作协程组 + workers: Scheduler<'p>, + //当前协程数 + running: AtomicUsize, + //尝试取出任务失败的次数 + pop_fail_times: AtomicUsize, + //最小协程数,即核心协程数 + min_size: AtomicUsize, + //最大协程数 + max_size: AtomicUsize, + //非核心协程的最大存活时间,单位ns + keep_alive_time: AtomicU64, + //阻滞器 + blocker: Arc, + //正在等待结果的 + waits: DashMap<&'p str, Arc<(Mutex, Condvar)>>, + //任务执行结果 + results: DashMap, &'p str>>, +} + +impl Drop for CoroutinePool<'_> { + fn drop(&mut self) { + if std::thread::panicking() { + return; + } + self.stop(Duration::from_secs(30)) + .unwrap_or_else(|_| panic!("Failed to stop coroutine pool {} !", self.name())); + assert_eq!( + PoolState::Stopped, + self.state(), + "The coroutine pool is not stopped !" + ); + assert_eq!( + 0, + self.get_running_size(), + "There are still tasks in progress !" + ); + assert_eq!( + 0, + self.task_queue.len(), + "There are still tasks to be carried out !" + ); + } +} + +impl Default for CoroutinePool<'_> { + fn default() -> Self { + Self::new( + format!("open-coroutine-pool-{:?}", std::thread::current().id()), + crate::common::constants::DEFAULT_STACK_SIZE, + 0, + 65536, + 0, + ) + } +} + +impl<'p> Deref for CoroutinePool<'p> { + type Target = Scheduler<'p>; + + fn deref(&self) -> &Self::Target { + &self.workers + } +} + +impl DerefMut for CoroutinePool<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.workers + } +} + +impl_for_named!(CoroutinePool<'p>); + +impl_current_for!(COROUTINE_POOL, CoroutinePool<'p>); + +impl_display_by_debug!(CoroutinePool<'p>); + +impl<'p> CoroutinePool<'p> { + /// Create a new `CoroutinePool` instance. + #[must_use] + pub fn new( + name: String, + stack_size: usize, + min_size: usize, + max_size: usize, + keep_alive_time: u64, + ) -> Self { + let mut workers = Scheduler::new(name, stack_size); + workers.add_listener(CoroutineCreator::default()); + CoroutinePool { + state: Cell::new(PoolState::Running), + workers, + running: AtomicUsize::new(0), + pop_fail_times: AtomicUsize::new(0), + min_size: AtomicUsize::new(min_size), + max_size: AtomicUsize::new(max_size), + task_queue: BeanFactory::get_or_default::>>( + crate::common::constants::TASK_GLOBAL_QUEUE_BEAN, + ) + .local_queue(), + keep_alive_time: AtomicU64::new(keep_alive_time), + blocker: Arc::default(), + results: DashMap::new(), + waits: DashMap::default(), + } + } + + /// Set the minimum coroutine number in this pool. + pub fn set_min_size(&self, min_size: usize) { + self.min_size.store(min_size, Ordering::Release); + } + + /// Get the minimum coroutine number in this pool + pub fn get_min_size(&self) -> usize { + self.min_size.load(Ordering::Acquire) + } + + /// Gets the number of coroutines currently running in this pool. + pub fn get_running_size(&self) -> usize { + self.running.load(Ordering::Acquire) + } + + /// Set the maximum coroutine number in this pool. + pub fn set_max_size(&self, max_size: usize) { + self.max_size.store(max_size, Ordering::Release); + } + + /// Get the maximum coroutine number in this pool. + pub fn get_max_size(&self) -> usize { + self.max_size.load(Ordering::Acquire) + } + + /// Set the maximum idle time running in this pool. + /// `keep_alive_time` has `ns` units. + pub fn set_keep_alive_time(&self, keep_alive_time: u64) { + self.keep_alive_time + .store(keep_alive_time, Ordering::Release); + } + + /// Get the maximum idle time running in this pool. + /// Returns in `ns` units. + pub fn get_keep_alive_time(&self) -> u64 { + self.keep_alive_time.load(Ordering::Acquire) + } + + /// Returns `true` if the task queue is empty. + pub fn is_empty(&self) -> bool { + self.size() == 0 + } + + /// Returns the number of tasks owned by this pool. + pub fn size(&self) -> usize { + self.task_queue.len() + } + + /// Stop this coroutine pool. + pub fn stop(&mut self, dur: Duration) -> std::io::Result<()> { + match self.state() { + PoolState::Running => { + assert_eq!(PoolState::Running, self.stopping()?); + _ = self.try_timed_schedule_task(dur)?; + assert_eq!(PoolState::Stopping, self.stopped()?); + Ok(()) + } + PoolState::Stopping => Err(Error::new(ErrorKind::Other, "should never happens")), + PoolState::Stopped => Ok(()), + } + } + + /// Submit a new task to this pool. + /// + /// Allow multiple threads to concurrently submit task to the pool, + /// but only allow one thread to execute scheduling. + pub fn submit_task( + &self, + name: Option, + func: impl FnOnce(Option) -> Option + 'p, + param: Option, + ) -> std::io::Result { + match self.state() { + PoolState::Running => {} + PoolState::Stopping | PoolState::Stopped => { + return Err(Error::new( + ErrorKind::Other, + "The coroutine pool is stopping or stopped !", + )) + } + } + let name = name.unwrap_or(format!("{}@{}", self.name(), uuid::Uuid::new_v4())); + self.submit_raw_task(Task::new(name.clone(), func, param)); + Ok(name) + } + + /// Submit new task to this pool. + /// + /// Allow multiple threads to concurrently submit task to the pool, + /// but only allow one thread to execute scheduling. + pub(crate) fn submit_raw_task(&self, task: Task<'p>) { + self.task_queue.push_back(task); + self.blocker.notify(); + } + + /// Attempt to obtain task results with the given `task_name`. + pub fn try_get_task_result(&self, task_name: &str) -> Option, &'p str>> { + self.results.remove(task_name).map(|(_, r)| r) + } + + /// Use the given `task_name` to obtain task results, and if no results are found, + /// block the current thread for `wait_time`. + /// + /// # Errors + /// if timeout + pub fn wait_task_result( + &self, + task_name: &str, + wait_time: Duration, + ) -> std::io::Result, &str>> { + let key = Box::leak(Box::from(task_name)); + if let Some(r) = self.try_get_task_result(key) { + self.notify(key); + drop(self.waits.remove(key)); + return Ok(r); + } + if SchedulableCoroutine::current().is_some() { + let timeout_time = get_timeout_time(wait_time); + loop { + _ = self.try_run(); + if let Some(r) = self.try_get_task_result(key) { + return Ok(r); + } + if timeout_time.saturating_sub(now()) == 0 { + return Err(Error::new(ErrorKind::TimedOut, "wait timeout")); + } + } + } + let arc = if let Some(arc) = self.waits.get(key) { + arc.clone() + } else { + let arc = Arc::new((Mutex::new(true), Condvar::new())); + assert!(self.waits.insert(key, arc.clone()).is_none()); + arc + }; + let (lock, cvar) = &*arc; + drop( + cvar.wait_timeout_while( + lock.lock() + .map_err(|e| Error::new(ErrorKind::Other, format!("{e}")))?, + wait_time, + |&mut pending| pending, + ) + .map_err(|e| Error::new(ErrorKind::Other, format!("{e}")))?, + ); + if let Some(r) = self.try_get_task_result(key) { + self.notify(key); + assert!(self.waits.remove(key).is_some()); + return Ok(r); + } + Err(Error::new(ErrorKind::TimedOut, "wait timeout")) + } + + fn can_recycle(&self) -> bool { + match self.state() { + PoolState::Running => false, + PoolState::Stopping | PoolState::Stopped => true, + } + } + + /// Try to create a coroutine in this pool. + /// + /// # Errors + /// if create failed. + fn try_grow(&self) -> std::io::Result<()> { + if self.task_queue.is_empty() { + // No task to run + trace!("The coroutine pool:{} has no task !", self.name()); + return Ok(()); + } + let create_time = now(); + self.submit_co( + move |suspender, ()| { + loop { + let pool = Self::current().expect("current pool not found"); + if pool.try_run().is_some() { + pool.reset_pop_fail_times(); + continue; + } + let running = pool.get_running_size(); + if now().saturating_sub(create_time) >= pool.get_keep_alive_time() + && running > pool.get_min_size() + || pool.can_recycle() + { + return None; + } + _ = pool.pop_fail_times.fetch_add(1, Ordering::Release); + match pool.pop_fail_times.load(Ordering::Acquire).cmp(&running) { + //让出CPU给下一个协程 + std::cmp::Ordering::Less => suspender.suspend(), + //减少CPU在N个无任务的协程中空轮询 + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => { + pool.blocker.clone().block(Duration::from_millis(1)); + pool.reset_pop_fail_times(); + } + } + } + }, + None, + ) + } + + /// Try to create a coroutine in this pool. + /// + /// # Errors + /// if create failed. + pub fn submit_co( + &self, + f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + 'static, + stack_size: Option, + ) -> std::io::Result<()> { + if self.get_running_size() >= self.get_max_size() { + trace!( + "The coroutine pool:{} has reached its maximum size !", + self.name() + ); + return Err(Error::new( + ErrorKind::Other, + "The coroutine pool has reached its maximum size !", + )); + } + self.deref().submit_co(f, stack_size).map(|()| { + _ = self.running.fetch_add(1, Ordering::Release); + }) + } + + fn reset_pop_fail_times(&self) { + self.pop_fail_times.store(0, Ordering::Release); + } + + fn try_run(&self) -> Option<()> { + self.task_queue.pop_front().map(|task| { + let (task_name, result) = task.run(); + assert!( + self.results.insert(task_name.clone(), result).is_none(), + "The previous result was not retrieved in a timely manner" + ); + self.notify(&task_name); + }) + } + + fn notify(&self, task_name: &str) { + if let Some(arc) = self.waits.get(task_name) { + let (lock, cvar) = &**arc; + let mut pending = lock.lock().expect("notify task failed"); + *pending = false; + cvar.notify_one(); + } + } + + /// Schedule the tasks. + /// + /// Allow multiple threads to concurrently submit task to the pool, + /// but only allow one thread to execute scheduling. + /// + /// # Errors + /// see `try_timeout_schedule`. + pub fn try_schedule_task(&mut self) -> std::io::Result<()> { + self.try_timeout_schedule_task(u64::MAX).map(|_| ()) + } + + /// Try scheduling the tasks for up to `dur`. + /// + /// Allow multiple threads to concurrently submit task to the scheduler, + /// but only allow one thread to execute scheduling. + /// + /// # Errors + /// see `try_timeout_schedule`. + pub fn try_timed_schedule_task(&mut self, dur: Duration) -> std::io::Result { + self.try_timeout_schedule_task(get_timeout_time(dur)) + } + + /// Attempt to schedule the tasks before the `timeout_time` timestamp. + /// + /// Allow multiple threads to concurrently submit task to the scheduler, + /// but only allow one thread to execute scheduling. + /// + /// Returns the left time in ns. + /// + /// # Errors + /// if change to ready fails. + pub fn try_timeout_schedule_task(&mut self, timeout_time: u64) -> std::io::Result { + match self.state() { + PoolState::Running | PoolState::Stopping => { + drop(self.try_grow()); + } + PoolState::Stopped => { + return Err(Error::new( + ErrorKind::Other, + "The coroutine pool is stopped !", + )) + } + } + Self::init_current(self); + let left_time = self.try_timeout_schedule(timeout_time); + Self::clean_current(); + left_time + } +} diff --git a/core/src/co_pool/state.rs b/core/src/co_pool/state.rs new file mode 100644 index 00000000..234448ed --- /dev/null +++ b/core/src/co_pool/state.rs @@ -0,0 +1,45 @@ +use crate::co_pool::CoroutinePool; +use crate::common::constants::PoolState; +use std::io::{Error, ErrorKind}; + +impl CoroutinePool<'_> { + /// running -> stopping + /// + /// # Errors + /// if change state fails. + pub(crate) fn stopping(&self) -> std::io::Result { + self.change_state(PoolState::Running, PoolState::Stopping) + } + + /// stopping -> stopped + /// + /// # Errors + /// if change state fails. + pub(crate) fn stopped(&self) -> std::io::Result { + self.change_state(PoolState::Stopping, PoolState::Stopped) + } + + /// Get the state of this coroutine. + pub fn state(&self) -> PoolState { + self.state.get() + } + + fn change_state( + &self, + old_state: PoolState, + new_state: PoolState, + ) -> std::io::Result { + let current = self.state(); + if current == new_state { + return Ok(old_state); + } + if current == old_state { + assert_eq!(old_state, self.state.replace(new_state)); + return Ok(old_state); + } + Err(Error::new( + ErrorKind::Other, + format!("{} unexpected {current}->{:?}", self.name(), new_state), + )) + } +} diff --git a/core/src/co_pool/task.rs b/core/src/co_pool/task.rs new file mode 100644 index 00000000..0553c749 --- /dev/null +++ b/core/src/co_pool/task.rs @@ -0,0 +1,79 @@ +use crate::catch; +use derivative::Derivative; + +/// 做C兼容时会用到 +pub type UserTaskFunc = extern "C" fn(usize) -> usize; + +/// The task impls. +#[repr(C)] +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Task<'t> { + name: String, + #[derivative(Debug = "ignore")] + func: Box) -> Option + 't>, + param: Option, +} + +impl<'t> Task<'t> { + /// Create a new `Task` instance. + pub fn new( + name: String, + func: impl FnOnce(Option) -> Option + 't, + param: Option, + ) -> Self { + Task { + name, + func: Box::new(func), + param, + } + } + + /// execute the task + /// + /// # Errors + /// if an exception occurred while executing this task. + pub fn run<'e>(self) -> (String, Result, &'e str>) { + ( + self.name.clone(), + catch!( + || (self.func)(self.param), + format!("task {} failed without message", self.name), + format!("task {}", self.name) + ), + ) + } +} + +#[cfg(test)] +mod tests { + use crate::co_pool::task::Task; + + #[test] + fn test() { + let task = Task::new( + String::from("test"), + |p| { + println!("hello"); + p + }, + None, + ); + assert_eq!((String::from("test"), Ok(None)), task.run()); + } + + #[test] + fn test_panic() { + let task = Task::new( + String::from("test"), + |_| { + panic!("test panic, just ignore it"); + }, + None, + ); + assert_eq!( + (String::from("test"), Err("test panic, just ignore it")), + task.run() + ); + } +} diff --git a/core/src/common/beans.rs b/core/src/common/beans.rs new file mode 100644 index 00000000..67dcbbb7 --- /dev/null +++ b/core/src/common/beans.rs @@ -0,0 +1,104 @@ +use dashmap::DashMap; +use std::ffi::c_void; +use std::sync::atomic::{AtomicUsize, Ordering}; + +/// Simple bean factory. +#[repr(C)] +#[derive(Debug, Default)] +pub struct BeanFactory<'b>(DashMap<&'b str, usize>); + +impl BeanFactory<'_> { + fn get_instance<'i>() -> &'i BeanFactory<'i> { + static INSTANCE: AtomicUsize = AtomicUsize::new(0); + let mut ret = INSTANCE.load(Ordering::Relaxed); + if ret == 0 { + let ptr: &'i mut BeanFactory = Box::leak(Box::default()); + ret = std::ptr::from_mut(ptr) as usize; + INSTANCE.store(ret, Ordering::Relaxed); + } + unsafe { &*(ret as *mut BeanFactory) } + } + + /// Init bean if not exists. + pub fn init_bean(bean_name: &str, bean: B) { + let factory = Self::get_instance(); + if factory.0.get(bean_name).is_none() { + let bean: &B = Box::leak(Box::new(bean)); + assert!(factory + .0 + .insert( + Box::leak(Box::from(bean_name)), + std::ptr::from_ref(bean) as usize, + ) + .is_none()); + } + } + + /// Remove bean if exists. + #[must_use] + pub fn remove_bean(bean_name: &str) -> Option { + Self::get_instance() + .0 + .remove(bean_name) + .map(|(_, ptr)| unsafe { *Box::from_raw((ptr as *mut c_void).cast::()) }) + } + + /// Get the bean by name. + #[must_use] + pub fn get_bean(bean_name: &str) -> Option<&B> { + Self::get_instance() + .0 + .get(bean_name) + .map(|ptr| unsafe { &*(*ptr as *mut c_void).cast::() }) + } + + /// Get the bean by name. + /// + /// # Safety + /// Only one mutable reference can be held for a given bean at a time. + #[must_use] + pub unsafe fn get_mut_bean(bean_name: &str) -> Option<&mut B> { + Self::get_instance() + .0 + .get_mut(bean_name) + .map(|ptr| &mut *(*ptr as *mut c_void).cast::()) + } + + /// Get the bean by name, create bean if not exists. + #[must_use] + pub fn get_or_default(bean_name: &str) -> &B { + let factory = Self::get_instance(); + factory.0.get(bean_name).map_or_else( + || { + let bean: &B = Box::leak(Box::default()); + _ = factory.0.insert( + Box::leak(Box::from(bean_name)), + std::ptr::from_ref(bean) as usize, + ); + bean + }, + |ptr| unsafe { &*(*ptr as *mut c_void).cast::() }, + ) + } + + /// Get the bean by name, create bean if not exists. + /// + /// # Safety + /// Only one mutable reference can be held for a given bean at a time. + #[must_use] + #[allow(clippy::mut_from_ref)] + pub unsafe fn get_mut_or_default(bean_name: &str) -> &mut B { + let factory = Self::get_instance(); + factory.0.get_mut(bean_name).map_or_else( + || { + let bean: &mut B = Box::leak(Box::default()); + _ = factory.0.insert( + Box::leak(Box::from(bean_name)), + std::ptr::from_ref(bean) as usize, + ); + bean + }, + |ptr| &mut *(*ptr as *mut c_void).cast::(), + ) + } +} diff --git a/open-coroutine-core/src/constants.rs b/core/src/common/constants.rs similarity index 55% rename from open-coroutine-core/src/constants.rs rename to core/src/common/constants.rs index 044d4e81..9ceb89d1 100644 --- a/open-coroutine-core/src/constants.rs +++ b/core/src/common/constants.rs @@ -1,11 +1,46 @@ use crate::impl_display_by_debug; -use std::fmt::Debug; +use once_cell::sync::Lazy; +use std::time::Duration; -/// min stack size for backtrace -pub const DEFAULT_STACK_SIZE: usize = 64 * 1024; +/// Recommended stack size for coroutines. +pub const DEFAULT_STACK_SIZE: usize = 128 * 1024; -/// CPU bound to monitor -pub const MONITOR_CPU: usize = 0; +/// A user data used to indicate the timeout of `io_uring_enter`. +#[cfg(all(target_os = "linux", feature = "io_uring"))] +pub const IO_URING_TIMEOUT_USERDATA: usize = usize::MAX - 1; + +/// Coroutine global queue bean name. +pub const COROUTINE_GLOBAL_QUEUE_BEAN: &str = "coroutineGlobalQueueBean"; + +/// Task global queue bean name. +pub const TASK_GLOBAL_QUEUE_BEAN: &str = "taskGlobalQueueBean"; + +/// Monitor bean name. +pub const MONITOR_BEAN: &str = "monitorBean"; + +/// Default time slice. +pub const SLICE: Duration = Duration::from_millis(10); + +/// Get the cpu count +#[must_use] +pub fn cpu_count() -> usize { + static CPU_COUNT: Lazy = Lazy::new(num_cpus::get); + *CPU_COUNT +} + +/// Enums used to describe pool state +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum PoolState { + /// The pool is running. + Running, + /// The pool is stopping. + Stopping, + /// The pool is stopped. + Stopped, +} + +impl_display_by_debug!(PoolState); /// Enums used to describe syscall #[allow(non_camel_case_types, missing_docs)] @@ -41,6 +76,8 @@ pub enum Syscall { #[cfg(windows)] iocp, recv, + #[cfg(windows)] + WSARecv, recvfrom, read, pread, @@ -53,7 +90,13 @@ pub enum Syscall { shutdown, close, socket, + #[cfg(windows)] + WSASocketW, + #[cfg(windows)] + ioctlsocket, send, + #[cfg(windows)] + WSASend, sendto, write, pwrite, @@ -66,10 +109,47 @@ pub enum Syscall { renameat2, mkdirat, openat, + pthread_cond_timedwait, + pthread_mutex_trylock, + pthread_mutex_lock, + pthread_mutex_unlock, +} + +impl Syscall { + /// Get the `NIO` syscall. + #[must_use] + pub fn nio() -> Self { + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + Self::epoll_wait + } else if #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd" + ))] { + Self::kevent + } else if #[cfg(windows)] { + Self::iocp + } else { + compile_error!("unsupported") + } + } + } } impl_display_by_debug!(Syscall); +impl From for &str { + fn from(val: Syscall) -> Self { + format!("{val}").leak() + } +} + /// Enums used to describe syscall state #[repr(C)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -91,8 +171,6 @@ impl_display_by_debug!(SyscallState); #[repr(C)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CoroutineState { - ///The coroutine is created. - Created, ///The coroutine is ready to run. Ready, ///The coroutine is running. @@ -108,35 +186,3 @@ pub enum CoroutineState { } impl_display_by_debug!(CoroutineState); - -/// Enums used to describe pool state -#[repr(C)] -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum PoolState { - ///The pool is created. - Created, - ///The pool is running, `true` means thread mode. - Running(bool), - ///The pool is stopping, `true` means thread mode. - Stopping(bool), - ///The pool is stopped. - Stopped, -} - -impl_display_by_debug!(PoolState); - -/// Enums used to describe monitor state -#[repr(C)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum MonitorState { - /// The monitor is created. - Created, - /// The monitor is running. - Running, - /// The monitor is stopping. - Stopping, - /// The monitor is stopped. - Stopped, -} - -impl_display_by_debug!(MonitorState); diff --git a/core/src/common/macros.rs b/core/src/common/macros.rs new file mode 100644 index 00000000..7c40fcb0 --- /dev/null +++ b/core/src/common/macros.rs @@ -0,0 +1,171 @@ +/// Constructs an event at the trace level. +#[allow(unused_macros)] +#[macro_export] +macro_rules! trace { + ($( $args:expr ),*) => { + #[cfg(all(debug_assertions, feature = "log"))] + tracing::trace!( $( $args ),* ); + } +} + +/// Constructs an event at the info level. +#[allow(unused_macros)] +#[macro_export] +macro_rules! info { + ($( $args:expr ),*) => { + #[cfg(feature = "log")] + tracing::info!( $( $args ),* ); + } +} + +/// Constructs an event at the warn level. +#[allow(unused_macros)] +#[macro_export] +macro_rules! warn { + ($( $args:expr ),*) => { + #[cfg(feature = "log")] + tracing::warn!( $( $args ),* ); + } +} + +/// Constructs an event at the error level. +#[allow(unused_macros)] +#[macro_export] +macro_rules! error { + ($( $args:expr ),*) => { + #[cfg(feature = "log")] + tracing::error!( $( $args ),* ); + } +} + +/// Catch panic. +#[macro_export] +macro_rules! catch { + ($f:expr, $msg:expr, $arg:expr) => { + std::panic::catch_unwind(std::panic::AssertUnwindSafe($f)).map_err(|e| { + let message = if let Some(msg) = e.downcast_ref::<&'static str>() { + *msg + } else { + $msg.leak() + }; + $crate::error!("{} failed with error:{}", $arg, message); + message + }) + }; +} + +/// Fast impl `Display` trait for `Debug` types. +#[allow(unused_macros)] +#[macro_export] +macro_rules! impl_display_by_debug { + ($struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? + $(where $( + $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? + ),+)? + ) => { + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? std::fmt::Display + for $struct_name$(<$($generic1),+>)? + where + $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? + $struct_name$(<$($generic1),+>)?: std::fmt::Debug, + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } + } + }; +} + +/// Fast impl `Current` for a type. +/// This crate use `std` cause `#![no_std]` not support `thread_local!`. +#[allow(unused_macros)] +#[macro_export] +macro_rules! impl_current_for { + ( + $name:ident, + $struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? + $(where $( + $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? + ),+)? + ) => { + thread_local! { + static $name: std::cell::RefCell> = const { std::cell::RefCell::new(std::collections::VecDeque::new()) }; + } + + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? $struct_name$(<$($generic1),+>)? + $(where $($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+)? + { + /// Init the current. + pub(crate) fn init_current(current: &Self) { + $name.with(|s| { + s.borrow_mut() + .push_front(core::ptr::from_ref(current).cast::()); + }); + } + + /// Get the current if has. + #[must_use] + #[allow(unreachable_pub)] + pub fn current<'current>() -> Option<&'current Self> { + $name.with(|s| { + s.borrow() + .front() + .map(|ptr| unsafe { &*(*ptr).cast::() }) + }) + } + + /// Clean the current. + pub(crate) fn clean_current() { + $name.with(|s| _ = s.borrow_mut().pop_front()); + } + } + }; +} + +/// Fast impl common traits for `Named` types. +/// Check for help information. +#[macro_export] +macro_rules! impl_for_named { + ($struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? + $(where $( + $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? + ),+)? + ) => { + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? Eq + for $struct_name$(<$($generic1),+>)? + { + } + + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? PartialEq + for $struct_name$(<$($generic1),+>)? + { + fn eq(&self, other: &Self) -> bool { + self.name().eq(other.name()) + } + } + + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? Ord + for $struct_name$(<$($generic1),+>)? + { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.name().cmp(other.name()) + } + } + + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? PartialOrd + for $struct_name$(<$($generic1),+>)? + { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? std::hash::Hash + for $struct_name$(<$($generic1),+>)? + { + fn hash(&self, state: &mut H) { + self.name().hash(state) + } + } + }; +} diff --git a/core/src/common/mod.rs b/core/src/common/mod.rs new file mode 100644 index 00000000..0e7aff9d --- /dev/null +++ b/core/src/common/mod.rs @@ -0,0 +1,204 @@ +#[cfg(target_os = "linux")] +use std::ffi::c_int; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// Constants. +pub mod constants; + +/// Check for help information. +pub(crate) mod macros; + +/// `BeanFactory` impls. +pub mod beans; + +/// `TimerList` impls. +pub mod timer; + +/// Suppose a thread in a work-stealing scheduler is idle and looking for the next task to run. To +/// find an available task, it might do the following: +/// +/// 1. Try popping one task from the local worker queue. +/// 2. Try popping and stealing tasks from another local worker queue. +/// 3. Try popping and stealing a batch of tasks from the global injector queue. +/// +/// A queue implementation of work-stealing strategy: +/// +/// # Examples +/// +/// ``` +/// use open_coroutine_core::common::work_steal::WorkStealQueue; +/// +/// let queue = WorkStealQueue::new(2, 64); +/// queue.push(6); +/// queue.push(7); +/// +/// let local0 = queue.local_queue(); +/// local0.push_back(2); +/// local0.push_back(3); +/// local0.push_back(4); +/// local0.push_back(5); +/// +/// let local1 = queue.local_queue(); +/// local1.push_back(0); +/// local1.push_back(1); +/// for i in 0..8 { +/// assert_eq!(local1.pop_front(), Some(i)); +/// } +/// assert_eq!(local0.pop_front(), None); +/// assert_eq!(local1.pop_front(), None); +/// assert_eq!(queue.pop(), None); +/// ``` +/// +pub mod work_steal; + +#[cfg(target_os = "linux")] +extern "C" { + fn linux_version_code() -> c_int; +} + +/// Get linux kernel version number. +#[must_use] +#[cfg(target_os = "linux")] +pub fn kernel_version(major: c_int, patchlevel: c_int, sublevel: c_int) -> c_int { + ((major) << 16) + ((patchlevel) << 8) + if (sublevel) > 255 { 255 } else { sublevel } +} + +/// Get current linux kernel version number. +#[must_use] +#[cfg(target_os = "linux")] +pub fn current_kernel_version() -> c_int { + unsafe { linux_version_code() } +} + +/// get the current wall clock in ns +/// +/// # Panics +/// if the time is before `UNIX_EPOCH` +#[must_use] +pub fn now() -> u64 { + u64::try_from( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("1970-01-01 00:00:00 UTC was {} seconds ago!") + .as_nanos(), + ) + .unwrap_or(u64::MAX) +} + +/// current ns time add `dur`. +#[must_use] +pub fn get_timeout_time(dur: Duration) -> u64 { + u64::try_from(dur.as_nanos()) + .map(|d| d.saturating_add(now())) + .unwrap_or(u64::MAX) +} + +/// Make the total time into slices. +#[must_use] +pub fn get_slices(total: Duration, slice: Duration) -> Vec { + let mut result = Vec::new(); + if Duration::ZERO == total { + return result; + } + let mut left_total = total; + while left_total > slice { + result.push(slice); + if let Some(new_left_total) = left_total.checked_sub(slice) { + left_total = new_left_total; + } + } + result.push(left_total); + result +} + +/// Get the page size of this system. +pub fn page_size() -> usize { + static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); + let mut ret = PAGE_SIZE.load(Ordering::Relaxed); + if ret == 0 { + unsafe { + cfg_if::cfg_if! { + if #[cfg(windows)] { + let mut info = std::mem::zeroed(); + windows_sys::Win32::System::SystemInformation::GetSystemInfo(&mut info); + ret = usize::try_from(info.dwPageSize).expect("get page size failed"); + } else { + ret = usize::try_from(libc::sysconf(libc::_SC_PAGESIZE)).expect("get page size failed"); + } + } + } + PAGE_SIZE.store(ret, Ordering::Relaxed); + } + ret +} + +/// Recommended read zone size for coroutines. +pub fn default_red_zone() -> usize { + static DEFAULT_RED_ZONE: AtomicUsize = AtomicUsize::new(0); + let mut ret = DEFAULT_RED_ZONE.load(Ordering::Relaxed); + if ret == 0 { + cfg_if::cfg_if! { + if #[cfg(windows)] { + ret = 32 * 1024 + page_size() * 3; + } else { + ret = 16 * 1024 + page_size(); + } + } + DEFAULT_RED_ZONE.store(ret, Ordering::Relaxed); + } + ret +} + +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default)] +pub struct CondvarBlocker { + mutex: std::sync::Mutex, + condvar: std::sync::Condvar, +} + +impl CondvarBlocker { + /// Block current thread for a while. + pub fn block(&self, dur: Duration) { + _ = self.condvar.wait_timeout_while( + self.mutex.lock().expect("lock failed"), + dur, + |&mut condition| !condition, + ); + let mut condition = self.mutex.lock().expect("lock failed"); + *condition = false; + } + + /// Notify by other thread. + pub fn notify(&self) { + let mut condition = self.mutex.lock().expect("lock failed"); + // true means the condition is ready, the other thread can continue. + *condition = true; + self.condvar.notify_one(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + #[test] + fn blocker() { + let start = now(); + let blocker = Arc::new(CondvarBlocker::default()); + let clone = blocker.clone(); + _ = std::thread::spawn(move || { + blocker.notify(); + }); + clone.block(Duration::from_secs(3)); + assert!(now() - start < 1_000_000_000); + } + + #[cfg(target_os = "linux")] + #[test] + fn test() { + assert!(current_kernel_version() > kernel_version(2, 7, 0)) + } +} diff --git a/open-coroutine-timer/src/lib.rs b/core/src/common/timer.rs similarity index 54% rename from open-coroutine-timer/src/lib.rs rename to core/src/common/timer.rs index 720d651e..e912b830 100644 --- a/open-coroutine-timer/src/lib.rs +++ b/core/src/common/timer.rs @@ -1,98 +1,28 @@ -#![deny( - // The following are allowed by default lints according to - // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html - anonymous_parameters, - bare_trait_objects, - elided_lifetimes_in_paths, - missing_copy_implementations, - missing_debug_implementations, - missing_docs, - single_use_lifetimes, - trivial_casts, - trivial_numeric_casts, - unreachable_pub, - unsafe_code, - unstable_features, - unused_extern_crates, - unused_import_braces, - unused_qualifications, - unused_results, - variant_size_differences, - warnings, // treat all wanings as errors - - clippy::all, - // clippy::restriction, - clippy::pedantic, - // clippy::nursery, // It's still under development - clippy::cargo, -)] -#![allow( - // Some explicitly allowed Clippy lints, must have clear reason to allow - clippy::blanket_clippy_restriction_lints, // allow clippy::restriction - clippy::implicit_return, // actually omitting the return keyword is idiomatic Rust code - clippy::module_name_repetitions, // repeation of module name in a struct name is not big deal - clippy::multiple_crate_versions, // multi-version dependency crates is not able to fix - clippy::panic_in_result_fn, - clippy::shadow_same, // Not too much bad - clippy::shadow_reuse, // Not too much bad - clippy::exhaustive_enums, - clippy::exhaustive_structs, - clippy::indexing_slicing, - clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix - clippy::single_char_lifetime_names, -)] - -//! Associate `VecDeque` with `timestamps`. -use std::collections::vec_deque::{Iter, IterMut}; +use crate::impl_display_by_debug; +use derivative::Derivative; use std::collections::{BTreeMap, VecDeque}; +use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -/// get the current wall clock in ns -/// -/// # Panics -/// if the time is before `UNIX_EPOCH` -#[must_use] -pub fn now() -> u64 { - u64::try_from( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("1970-01-01 00:00:00 UTC was {} seconds ago!") - .as_nanos(), - ) - .unwrap_or(u64::MAX) -} - -/// current ns time add `dur`. -#[must_use] -pub fn get_timeout_time(dur: Duration) -> u64 { - u64::try_from(dur.as_nanos()) - .map(|d| d.saturating_add(now())) - .unwrap_or(u64::MAX) -} /// A queue for managing multiple entries under a specified timestamp. +#[repr(C)] #[derive(Debug, Eq, PartialEq)] pub struct TimerEntry { timestamp: u64, inner: VecDeque, } -impl<'t, T> IntoIterator for &'t mut TimerEntry { - type Item = &'t mut T; - type IntoIter = IterMut<'t, T>; +impl Deref for TimerEntry { + type Target = VecDeque; - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() + fn deref(&self) -> &Self::Target { + &self.inner } } -impl<'t, T> IntoIterator for &'t TimerEntry { - type Item = &'t T; - type IntoIter = Iter<'t, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() +impl DerefMut for TimerEntry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } @@ -106,34 +36,12 @@ impl TimerEntry { } } - /// Returns the number of elements in the deque. - #[must_use] - pub fn len(&self) -> usize { - self.inner.len() - } - - /// Returns `true` if the deque is empty. - #[must_use] - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - /// Get the timestamp. #[must_use] pub fn get_timestamp(&self) -> u64 { self.timestamp } - /// Removes the first element and returns it, or `None` if the deque is empty. - pub fn pop_front(&mut self) -> Option { - self.inner.pop_front() - } - - /// Appends an element to the back of the deque. - pub fn push_back(&mut self, t: T) { - self.inner.push_back(t); - } - /// Removes and returns the `t` from the deque. /// Whichever end is closer to the removal point will be moved to make /// room, and all the affected elements will be moved to new positions. @@ -148,35 +56,20 @@ impl TimerEntry { .unwrap_or_else(|x| x); self.inner.remove(index) } - - /// Returns a front-to-back iterator that returns mutable references. - pub fn iter_mut(&mut self) -> IterMut<'_, T> { - self.inner.iter_mut() - } - - /// Returns a front-to-back iterator. - #[must_use] - pub fn iter(&self) -> Iter<'_, T> { - self.inner.iter() - } } +impl_display_by_debug!(TimerEntry); + /// A queue for managing multiple `TimerEntry`. #[repr(C)] -#[derive(Debug)] +#[derive(Derivative)] +#[derivative(Debug, Eq, PartialEq)] pub struct TimerList { inner: BTreeMap>, + #[derivative(PartialEq = "ignore")] total: AtomicUsize, } -impl PartialEq for TimerList { - fn eq(&self, other: &Self) -> bool { - self.inner.eq(&other.inner) - } -} - -impl Eq for TimerList {} - impl Default for TimerList { fn default() -> Self { TimerList { @@ -186,12 +79,17 @@ impl Default for TimerList { } } -impl<'t, T> IntoIterator for &'t TimerList { - type Item = (&'t u64, &'t TimerEntry); - type IntoIter = std::collections::btree_map::Iter<'t, u64, TimerEntry>; +impl Deref for TimerList { + type Target = BTreeMap>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} - fn into_iter(self) -> Self::IntoIter { - self.iter() +impl DerefMut for TimerList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } @@ -257,9 +155,8 @@ impl TimerList { /// room, and all the affected elements will be moved to new positions. /// Returns `None` if `timestamp` is out of bounds. pub fn remove_entry(&mut self, timestamp: &u64) -> Option> { - self.inner.remove(timestamp).map(|entry| { + self.inner.remove(timestamp).inspect(|entry| { _ = self.total.fetch_sub(entry.len(), Ordering::Release); - entry }) } @@ -272,9 +169,8 @@ impl TimerList { T: Ord, { if let Some(entry) = self.inner.get_mut(timestamp) { - let val = entry.remove(t).map(|item| { + let val = entry.remove(t).inspect(|_| { _ = self.total.fetch_sub(1, Ordering::Release); - item }); if entry.is_empty() { _ = self.remove_entry(timestamp); @@ -283,16 +179,14 @@ impl TimerList { } None } - - /// Returns a front-to-back iterator. - pub fn iter(&self) -> std::collections::btree_map::Iter<'_, u64, TimerEntry> { - self.inner.iter() - } } +impl_display_by_debug!(TimerList); + #[cfg(test)] mod tests { - use super::*; + use crate::common::now; + use crate::common::timer::TimerList; #[test] fn test() { diff --git a/open-coroutine-queue/src/work_steal.rs b/core/src/common/work_steal.rs similarity index 86% rename from open-coroutine-queue/src/work_steal.rs rename to core/src/common/work_steal.rs index d64887db..93ac58df 100644 --- a/open-coroutine-queue/src/work_steal.rs +++ b/core/src/common/work_steal.rs @@ -1,5 +1,5 @@ -use crate::rand::{FastRand, RngSeedGenerator}; use crossbeam_deque::{Injector, Steal}; +use rand::Rng; use st3::fifo::Worker; use std::collections::VecDeque; use std::fmt::Debug; @@ -15,7 +15,6 @@ pub struct WorkStealQueue { len: AtomicUsize, local_queues: VecDeque>, index: AtomicUsize, - seed_generator: RngSeedGenerator, } impl Drop for WorkStealQueue { @@ -30,19 +29,6 @@ impl Drop for WorkStealQueue { } impl WorkStealQueue { - /// Get a global `WorkStealQueue` instance. - #[allow(unsafe_code, trivial_casts)] - pub fn get_instance<'s>() -> &'s WorkStealQueue { - static INSTANCE: AtomicUsize = AtomicUsize::new(0); - let mut ret = INSTANCE.load(Ordering::Relaxed); - if ret == 0 { - let ptr: &'s mut WorkStealQueue = Box::leak(Box::default()); - ret = std::ptr::from_mut::>(ptr) as usize; - INSTANCE.store(ret, Ordering::Relaxed); - } - unsafe { &*(ret as *mut WorkStealQueue) } - } - /// Create a new `WorkStealQueue` instance. #[must_use] pub fn new(local_queues_size: usize, local_capacity: usize) -> Self { @@ -53,7 +39,6 @@ impl WorkStealQueue { .map(|_| Worker::new(local_capacity)) .collect(), index: AtomicUsize::new(0), - seed_generator: RngSeedGenerator::default(), } } @@ -107,7 +92,7 @@ impl WorkStealQueue { .local_queues .get(index) .unwrap_or_else(|| panic!("local queue {index} init failed!")); - LocalQueue::new(self, local, FastRand::new(self.seed_generator.next_seed())) + LocalQueue::new(self, local) } } @@ -126,14 +111,6 @@ pub struct LocalQueue<'l, T: Debug> { shared: &'l WorkStealQueue, stealing: AtomicBool, queue: &'l Worker, - /// Fast random number generator. - rand: FastRand, -} - -impl Default for LocalQueue<'_, T> { - fn default() -> Self { - WorkStealQueue::get_instance().local_queue() - } } impl Drop for LocalQueue<'_, T> { @@ -145,13 +122,12 @@ impl Drop for LocalQueue<'_, T> { } impl<'l, T: Debug> LocalQueue<'l, T> { - fn new(shared: &'l WorkStealQueue, queue: &'l Worker, rand: FastRand) -> Self { + fn new(shared: &'l WorkStealQueue, queue: &'l Worker) -> Self { LocalQueue { tick: AtomicU32::new(0), shared, stealing: AtomicBool::new(false), queue, - rand, } } @@ -165,7 +141,7 @@ impl<'l, T: Debug> LocalQueue<'l, T> { /// # Examples /// /// ``` - /// use open_coroutine_queue::WorkStealQueue; + /// use open_coroutine_core::common::work_steal::WorkStealQueue; /// /// let queue = WorkStealQueue::new(1, 2); /// let local = queue.local_queue(); @@ -205,7 +181,7 @@ impl<'l, T: Debug> LocalQueue<'l, T> { /// # Examples /// /// ``` - /// use open_coroutine_queue::WorkStealQueue; + /// use open_coroutine_core::common::work_steal::WorkStealQueue; /// /// let queue = WorkStealQueue::new(1, 2); /// let local = queue.local_queue(); @@ -248,7 +224,7 @@ impl<'l, T: Debug> LocalQueue<'l, T> { /// # Examples /// /// ``` - /// use open_coroutine_queue::WorkStealQueue; + /// use open_coroutine_core::common::work_steal::WorkStealQueue; /// /// let queue = WorkStealQueue::new(1, 32); /// for i in 0..4 { @@ -264,7 +240,8 @@ impl<'l, T: Debug> LocalQueue<'l, T> { /// /// # Examples /// ``` - /// use open_coroutine_queue::WorkStealQueue; + /// use open_coroutine_core::common::work_steal::WorkStealQueue; + /// /// let queue = WorkStealQueue::new(2, 64); /// let local0 = queue.local_queue(); /// local0.push_back(2); @@ -281,7 +258,6 @@ impl<'l, T: Debug> LocalQueue<'l, T> { /// assert_eq!(local1.pop_front(), None); /// assert_eq!(queue.pop(), None); /// ``` - #[allow(clippy::cast_possible_truncation)] pub fn pop_front(&self) -> Option { //每从本地弹出61次,就从全局队列弹出 if self.tick() % 61 == 0 { @@ -289,7 +265,6 @@ impl<'l, T: Debug> LocalQueue<'l, T> { return Some(val); } } - //从本地队列弹出元素 if let Some(val) = self.queue.pop() { return Some(val); @@ -298,7 +273,7 @@ impl<'l, T: Debug> LocalQueue<'l, T> { //尝试从其他本地队列steal let local_queues = &self.shared.local_queues; let num = local_queues.len(); - let start = self.rand.fastrand_n(num as u32) as usize; + let start = rand::thread_rng().gen_range(0..num); for i in 0..num { let i = (start + i) % num; if let Some(another) = local_queues.get(i) { diff --git a/open-coroutine-core/src/coroutine/korosensei.rs b/core/src/coroutine/korosensei.rs similarity index 84% rename from open-coroutine-core/src/coroutine/korosensei.rs rename to core/src/coroutine/korosensei.rs index 2f8139f1..1def0b2a 100644 --- a/open-coroutine-core/src/coroutine/korosensei.rs +++ b/core/src/coroutine/korosensei.rs @@ -1,17 +1,15 @@ use crate::catch; -use crate::common::{Current, Named}; -use crate::constants::CoroutineState; +use crate::common::constants::CoroutineState; use crate::coroutine::listener::Listener; use crate::coroutine::local::CoroutineLocal; use crate::coroutine::suspender::Suspender; -use corosensei::stack::DefaultStack; +use corosensei::stack::{DefaultStack, Stack}; use corosensei::trap::TrapHandlerRegs; -use corosensei::{CoroutineResult, ScopedCoroutine}; -use std::cell::Cell; +use corosensei::CoroutineResult; +use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::fmt::Debug; use std::io::{Error, ErrorKind}; -use std::panic::UnwindSafe; cfg_if::cfg_if! { if #[cfg(unix)] { @@ -24,25 +22,17 @@ cfg_if::cfg_if! { /// Use `corosensei` as the low-level coroutine. #[repr(C)] -pub struct Coroutine<'c, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ +pub struct Coroutine<'c, Param, Yield, Return> { pub(crate) name: String, - inner: ScopedCoroutine<'c, Param, Yield, Result, DefaultStack>, + inner: corosensei::Coroutine, DefaultStack>, pub(crate) state: Cell>, - pub(crate) listeners: VecDeque<&'c dyn Listener>, + pub(crate) stack_size: usize, + pub(crate) stack_bottom: RefCell>, + pub(crate) listeners: VecDeque<&'c dyn Listener>, pub(crate) local: CoroutineLocal<'c>, } -impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ +impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> { cfg_if::cfg_if! { if #[cfg(unix)] { #[allow( @@ -101,7 +91,6 @@ where // assert!(handler.stack_ptr_in_bounds(sp), "coroutine {} stack overflow !", co.get_name()); // let regs = handler.setup_trap_handler(|| Err("invalid memory reference")); let stack_ptr_in_bounds = handler.stack_ptr_in_bounds(sp); - // todo here we can grow stack in the future let regs = handler.setup_trap_handler(move || { Err(if stack_ptr_in_bounds { "invalid memory reference" @@ -240,7 +229,6 @@ where // } // let regs = handler.setup_trap_handler(|| Err("invalid memory reference")); let stack_ptr_in_bounds = handler.stack_ptr_in_bounds(sp); - // todo here we can grow stack in the future let regs = handler.setup_trap_handler(move || { Err(if stack_ptr_in_bounds { "invalid memory reference" @@ -258,10 +246,12 @@ where (*(*exception_info).ContextRecord).Rdi = rdi; (*(*exception_info).ContextRecord).Rsi = rsi; } else if #[cfg(target_arch = "x86")] { - let TrapHandlerRegs { eip, esp, ebp, ecx, edx } = regs; + let TrapHandlerRegs { eip, esp, ebp, eax, ebx, ecx, edx } = regs; (*(*exception_info).ContextRecord).Eip = eip; (*(*exception_info).ContextRecord).Esp = esp; (*(*exception_info).ContextRecord).Ebp = ebp; + (*(*exception_info).ContextRecord).Eax = eax; + (*(*exception_info).ContextRecord).Ebx = ebx; (*(*exception_info).ContextRecord).Ecx = ecx; (*(*exception_info).ContextRecord).Edx = edx; } else { @@ -309,17 +299,45 @@ where } } - pub(crate) fn add_raw_listener(&mut self, listener: &'c dyn Listener) { + pub(crate) fn add_raw_listener(&mut self, listener: &'c dyn Listener) { self.listeners.push_back(listener); } + + /// Grows the call stack if necessary. + /// + /// This function is intended to be called at manually instrumented points in a program where + /// recursion is known to happen quite a bit. This function will check to see if we're within + /// `red_zone` bytes of the end of the stack, and if so it will allocate a new stack of at least + /// `stack_size` bytes. + /// + /// The closure `f` is guaranteed to run on a stack with at least `red_zone` bytes, and it will be + /// run on the current stack if there's space available. + #[allow(clippy::inline_always)] + #[inline(always)] + pub fn maybe_grow_with R>( + red_zone: usize, + stack_size: usize, + callback: F, + ) -> std::io::Result { + // if we can't guess the remaining stack (unsupported on some platforms) we immediately grow + // the stack and then cache the new stack size (which we do know now because we allocated it. + if let Some(co) = Self::current() { + let remaining_stack = unsafe { co.remaining_stack() }; + if remaining_stack >= red_zone { + return Ok(callback()); + } + return DefaultStack::new(stack_size).map(|stack| { + co.stack_bottom.borrow_mut().push_front(stack.limit().get()); + let r = corosensei::on_stack(stack, callback); + _ = co.stack_bottom.borrow_mut().pop_front(); + r + }); + } + DefaultStack::new(stack_size).map(|stack| corosensei::on_stack(stack, callback)) + } } -impl Drop for Coroutine<'_, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ +impl Drop for Coroutine<'_, Param, Yield, Return> { fn drop(&mut self) { //for test_yield case if self.inner.started() && !self.inner.done() { @@ -328,11 +346,11 @@ where } } -impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> +impl Coroutine<'_, Param, Yield, Return> where - Param: UnwindSafe, - Yield: Debug + Copy + Eq + PartialEq + UnwindSafe, - Return: Debug + Copy + Eq + PartialEq + UnwindSafe, + Param: 'static, + Yield: Debug + Copy + Eq + 'static, + Return: Debug + Copy + Eq + 'static, { /// Create a new coroutine. /// @@ -340,36 +358,37 @@ where /// if stack allocate failed. pub fn new(name: String, f: F, stack_size: usize) -> std::io::Result where - F: FnOnce(&Suspender, Param) -> Return, - F: UnwindSafe, - F: 'c, + F: FnOnce(&Suspender, Param) -> Return + 'static, { let stack_size = stack_size.max(crate::common::page_size()); let stack = DefaultStack::new(stack_size)?; - #[cfg(feature = "logs")] + let stack_bottom = RefCell::new(VecDeque::from([stack.limit().get()])); let co_name = name.clone().leak(); - let inner = ScopedCoroutine::with_stack(stack, move |y, p| { - let suspender = Suspender::new(y); - Suspender::::init_current(&suspender); - let r = catch!( - || f(&suspender, p), - "coroutine failed without message", - "coroutine", + let inner = corosensei::Coroutine::with_stack(stack, move |y, p| { + catch!( + move || { + let suspender = Suspender::new(y); + Suspender::::init_current(&suspender); + let r = f(&suspender, p); + Suspender::::clean_current(); + r + }, + format!("coroutine {co_name} failed without message"), co_name - ); - Suspender::::clean_current(); - r + ) }); #[allow(unused_mut)] let mut co = Coroutine { name, inner, - state: Cell::new(CoroutineState::Created), + stack_size, + stack_bottom, + state: Cell::new(CoroutineState::Ready), listeners: VecDeque::default(), local: CoroutineLocal::default(), }; - #[cfg(all(unix, feature = "preemptive-schedule"))] - co.add_listener(crate::monitor::creator::MonitorListener::default()); + #[cfg(all(unix, feature = "preemptive"))] + co.add_listener(crate::monitor::MonitorListener::default()); co.on_create(&co, stack_size); Ok(co) } @@ -393,7 +412,7 @@ where } _ => Err(Error::new( ErrorKind::Other, - format!("{} unexpected state {current}", self.get_name()), + format!("{} unexpected state {current}", self.name()), )), } } diff --git a/core/src/coroutine/listener.rs b/core/src/coroutine/listener.rs new file mode 100644 index 00000000..34b4717b --- /dev/null +++ b/core/src/coroutine/listener.rs @@ -0,0 +1,110 @@ +use crate::common::constants::CoroutineState; +use crate::coroutine::local::CoroutineLocal; +use crate::coroutine::Coroutine; +use std::fmt::Debug; + +/// A trait mainly used for monitors. +#[allow(unused_variables)] +pub trait Listener: Debug { + /// Callback when the coroutine is created. + fn on_create(&self, local: &CoroutineLocal, stack_size: usize) {} + + /// Callback after changing the status of coroutine. + fn on_state_changed( + &self, + local: &CoroutineLocal, + old_state: CoroutineState, + new_state: CoroutineState, + ) { + } + + /// Callback after changing the coroutine status to ready. + fn on_ready(&self, local: &CoroutineLocal, old_state: CoroutineState) {} + + /// Callback after changing the coroutine status to running. + fn on_running(&self, local: &CoroutineLocal, old_state: CoroutineState) {} + + /// Callback after changing the coroutine status to suspend. + fn on_suspend(&self, local: &CoroutineLocal, old_state: CoroutineState) {} + + /// callback when the coroutine enters syscall. + fn on_syscall(&self, local: &CoroutineLocal, old_state: CoroutineState) {} + + /// Callback when the coroutine is completed. + fn on_complete( + &self, + local: &CoroutineLocal, + old_state: CoroutineState, + result: Return, + ) { + } + + /// Callback when the coroutine is completed with errors, usually, panic occurs. + fn on_error( + &self, + local: &CoroutineLocal, + old_state: CoroutineState, + message: &str, + ) { + } +} + +macro_rules! broadcast { + ($impl_method_name: ident($($arg: ident : $arg_type: ty),*), $method_name:expr) => { + fn $impl_method_name(&self, $($arg: $arg_type),*) { + for listener in &self.listeners { + _ = $crate::catch!( + || listener.$impl_method_name($($arg, )*), + format!("Listener {} failed without message", $method_name), + format!("{} invoke {}", self.name(), $method_name) + ); + } + } + } +} + +impl Listener for Coroutine<'_, Param, Yield, Return> +where + Yield: Debug + Copy, + Return: Debug + Copy, +{ + broadcast!(on_create(local: &CoroutineLocal, stack_size: usize), "on_create"); + + broadcast!(on_state_changed( + local: &CoroutineLocal, + old_state: CoroutineState, + new_state: CoroutineState + ), "on_state_changed"); + + broadcast!(on_ready( + local: &CoroutineLocal, + old_state: CoroutineState + ), "on_ready"); + + broadcast!(on_running( + local: &CoroutineLocal, + old_state: CoroutineState + ), "on_running"); + + broadcast!(on_suspend( + local: &CoroutineLocal, + old_state: CoroutineState + ), "on_suspend"); + + broadcast!(on_syscall( + local: &CoroutineLocal, + old_state: CoroutineState + ), "on_syscall"); + + broadcast!(on_complete( + local: &CoroutineLocal, + old_state: CoroutineState, + result: Return + ), "on_complete"); + + broadcast!(on_error( + local: &CoroutineLocal, + old_state: CoroutineState, + message: &str + ), "on_error"); +} diff --git a/open-coroutine-core/src/coroutine/local.rs b/core/src/coroutine/local.rs similarity index 56% rename from open-coroutine-core/src/coroutine/local.rs rename to core/src/coroutine/local.rs index be2727e9..8354f9ee 100644 --- a/open-coroutine-core/src/coroutine/local.rs +++ b/core/src/coroutine/local.rs @@ -1,47 +1,49 @@ +use crate::impl_display_by_debug; use dashmap::DashMap; use std::ffi::c_void; +use std::fmt::Debug; + +/// todo provide macro like [`std::thread_local`] /// A struct for coroutines handles local args. #[repr(C)] #[derive(Debug, Default)] pub struct CoroutineLocal<'c>(DashMap<&'c str, usize>); -#[allow(missing_docs)] -impl CoroutineLocal<'_> { - #[must_use] - pub fn put(&self, key: &str, val: V) -> Option { - let k: &str = Box::leak(Box::from(key)); +#[allow(clippy::must_use_candidate)] +impl<'c> CoroutineLocal<'c> { + /// Put a value into the coroutine local. + pub fn put(&self, key: &'c str, val: V) -> Option { let v = Box::leak(Box::new(val)); self.0 - .insert(k, std::ptr::from_mut::(v) as usize) + .insert(key, std::ptr::from_mut::(v) as usize) .map(|ptr| unsafe { *Box::from_raw((ptr as *mut c_void).cast::()) }) } - #[must_use] - pub fn get(&self, key: &str) -> Option<&V> { - let k: &str = Box::leak(Box::from(key)); + /// Get a value ref from the coroutine local. + pub fn get(&self, key: &'c str) -> Option<&V> { self.0 - .get(k) + .get(key) .map(|ptr| unsafe { &*(*ptr as *mut c_void).cast::() }) } - #[must_use] - pub fn get_mut(&self, key: &str) -> Option<&mut V> { - let k: &str = Box::leak(Box::from(key)); + /// Get a mut value ref from the coroutine local. + pub fn get_mut(&self, key: &'c str) -> Option<&mut V> { self.0 - .get(k) + .get(key) .map(|ptr| unsafe { &mut *(*ptr as *mut c_void).cast::() }) } - #[must_use] - pub fn remove(&self, key: &str) -> Option { - let k: &str = Box::leak(Box::from(key)); + /// Remove a key from the coroutine local. + pub fn remove(&self, key: &'c str) -> Option { self.0 - .remove(k) + .remove(key) .map(|ptr| unsafe { *Box::from_raw((ptr.1 as *mut c_void).cast::()) }) } } +impl_display_by_debug!(CoroutineLocal<'c>); + #[cfg(test)] mod tests { use super::*; diff --git a/open-coroutine-core/src/coroutine/mod.rs b/core/src/coroutine/mod.rs similarity index 53% rename from open-coroutine-core/src/coroutine/mod.rs rename to core/src/coroutine/mod.rs index bbb648ee..7b0389e9 100644 --- a/open-coroutine-core/src/coroutine/mod.rs +++ b/core/src/coroutine/mod.rs @@ -1,13 +1,12 @@ -use crate::common::{Current, Named}; -use crate::constants::CoroutineState; +use crate::common::constants::CoroutineState; +use crate::coroutine::listener::Listener; use crate::coroutine::local::CoroutineLocal; use crate::{impl_current_for, impl_display_by_debug, impl_for_named}; use std::fmt::{Debug, Formatter}; use std::ops::Deref; -use std::panic::UnwindSafe; -/// Coroutine suspender abstraction. -#[allow(missing_debug_implementations)] +/// Coroutine suspender abstraction and impl. +#[allow(dead_code)] pub mod suspender; /// Coroutine local abstraction. @@ -16,67 +15,92 @@ pub mod local; /// Coroutine listener abstraction and impl. pub mod listener; -/// Coroutine state abstraction and impl. -mod state; +#[cfg(feature = "korosensei")] +pub use korosensei::Coroutine; +#[cfg(feature = "korosensei")] +mod korosensei; /// Create a new coroutine. #[macro_export] macro_rules! co { ($f:expr, $size:literal $(,)?) => { $crate::coroutine::Coroutine::new(uuid::Uuid::new_v4().to_string(), $f, $size) - .expect("create coroutine failed !") }; ($f:expr $(,)?) => { $crate::coroutine::Coroutine::new( uuid::Uuid::new_v4().to_string(), $f, - $crate::constants::DEFAULT_STACK_SIZE, + $crate::common::constants::DEFAULT_STACK_SIZE, ) - .expect("create coroutine failed !") }; ($name:expr, $f:expr, $size:expr $(,)?) => { - $crate::coroutine::Coroutine::new($name, $f, $size).expect("create coroutine failed !") + $crate::coroutine::Coroutine::new($name, $f, $size) }; ($name:expr, $f:expr $(,)?) => { - $crate::coroutine::Coroutine::new($name, $f, $crate::constants::DEFAULT_STACK_SIZE) - .expect("create coroutine failed !") + $crate::coroutine::Coroutine::new($name, $f, $crate::common::constants::DEFAULT_STACK_SIZE) }; } -use crate::coroutine::listener::Listener; -#[cfg(feature = "korosensei")] -pub use korosensei::Coroutine; - -#[cfg(feature = "korosensei")] -mod korosensei; - -#[cfg(all(feature = "boost", not(feature = "korosensei")))] -mod boost {} +/// Coroutine state abstraction and impl. +mod state; -#[cfg(test)] -mod tests; +impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> { + /// Get the name of this coroutine. + pub fn name(&self) -> &str { + &self.name + } -impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ /// Returns the current state of this `StateCoroutine`. - pub fn state(&self) -> CoroutineState { + pub fn state(&self) -> CoroutineState + where + Yield: Copy, + Return: Copy, + { self.state.get() } /// Add a listener to this coroutine. - pub fn add_listener(&mut self, listener: impl Listener + 'c) { + pub fn add_listener(&mut self, listener: impl Listener + 'c) { self.add_raw_listener(Box::leak(Box::new(listener))); } + + /// Queries the amount of remaining stack as interpreted by this coroutine. + /// + /// This function will return the amount of stack space left which will be used + /// to determine whether a stack switch should be made or not. + /// + /// # Safety + /// + /// This can only be done safely in coroutine. + pub unsafe fn remaining_stack(&self) -> usize { + let current_ptr = psm::stack_pointer() as usize; + current_ptr - self.stack_bottom.borrow().front().copied().unwrap() + } + + /// Grows the call stack if necessary. + /// + /// This function is intended to be called at manually instrumented points in a program where + /// recursion is known to happen quite a bit. This function will check to see if we're within + /// `32 * 1024` bytes of the end of the stack, and if so it will allocate a new stack of at least + /// `128 * 1024` bytes. + /// + /// The closure `f` is guaranteed to run on a stack with at least `32 * 1024` bytes, and it will be + /// run on the current stack if there's space available. + #[allow(clippy::inline_always)] + #[inline(always)] + pub fn maybe_grow R>(callback: F) -> std::io::Result { + Self::maybe_grow_with( + crate::common::default_red_zone(), + crate::common::constants::DEFAULT_STACK_SIZE, + callback, + ) + } } impl Coroutine<'_, (), Yield, Return> where - Yield: Debug + Copy + UnwindSafe + Eq + PartialEq, - Return: Debug + Copy + UnwindSafe + Eq + PartialEq, + Yield: Debug + Copy + Eq + 'static, + Return: Debug + Copy + Eq + 'static, { /// A simpler version of [`Coroutine::resume_with`]. pub fn resume(&mut self) -> std::io::Result> { @@ -86,9 +110,9 @@ where impl Coroutine<'_, Param, Yield, Return> where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe + Eq + PartialEq, - Return: Debug + Copy + UnwindSafe + Eq + PartialEq, + Param: 'static, + Yield: Debug + Copy + Eq + 'static, + Return: Debug + Copy + Eq + 'static, { /// Resumes the execution of this coroutine. /// @@ -114,25 +138,19 @@ where impl Debug for Coroutine<'_, Param, Yield, Return> where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, + Yield: Debug + Copy, + Return: Debug + Copy, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Coroutine") - .field("name", &self.get_name()) + .field("name", &self.name()) .field("status", &self.state()) .field("local", &self.local) .finish() } } -impl<'c, Param, Yield, Return> Deref for Coroutine<'c, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ +impl<'c, Param, Yield, Return> Deref for Coroutine<'c, Param, Yield, Return> { type Target = CoroutineLocal<'c>; fn deref(&self) -> &Self::Target { @@ -140,38 +158,13 @@ where } } -impl Named for Coroutine<'_, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ - fn get_name(&self) -> &str { - &self.name - } -} - -impl_for_named!( - Coroutine<'c, Param, Yield, Return> - where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe -); - impl_display_by_debug!( Coroutine<'c, Param, Yield, Return> where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe + Yield: Debug + Copy, + Return: Debug + Copy ); -impl_current_for!( - COROUTINE, - Coroutine<'c, Param, Yield, Return> - where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe -); +impl_for_named!(Coroutine<'c, Param, Yield, Return>); + +impl_current_for!(COROUTINE, Coroutine<'c, Param, Yield, Return>); diff --git a/open-coroutine-core/src/coroutine/state.rs b/core/src/coroutine/state.rs similarity index 76% rename from open-coroutine-core/src/coroutine/state.rs rename to core/src/coroutine/state.rs index 9b9ed2b8..0d51916b 100644 --- a/open-coroutine-core/src/coroutine/state.rs +++ b/core/src/coroutine/state.rs @@ -1,17 +1,15 @@ -use crate::common::Named; -use crate::constants::{CoroutineState, Syscall, SyscallState}; +use crate::common::constants::{CoroutineState, Syscall, SyscallState}; +use crate::common::now; use crate::coroutine::listener::Listener; use crate::coroutine::Coroutine; use crate::{error, info}; use std::fmt::Debug; use std::io::{Error, ErrorKind}; -use std::panic::UnwindSafe; -impl<'c, Param, Yield, Return> Coroutine<'c, Param, Yield, Return> +impl Coroutine<'_, Param, Yield, Return> where - Param: UnwindSafe, - Yield: Copy + Eq + PartialEq + UnwindSafe + Debug, - Return: Copy + Eq + PartialEq + UnwindSafe + Debug, + Yield: Debug + Copy + Eq, + Return: Debug + Copy + Eq, { /// Returns the previous state of this `StateCoroutine`. /// Note: user should not use this method. @@ -22,42 +20,32 @@ where let old_state = self.state.replace(new_state); self.on_state_changed(self, old_state, new_state); if let CoroutineState::Error(_) = new_state { - error!("{} {:?}->{:?}", self.get_name(), old_state, new_state); + error!("{} {:?}->{:?}", self.name(), old_state, new_state); } else { - info!("{} {:?}->{:?}", self.get_name(), old_state, new_state); + info!("{} {:?}->{:?}", self.name(), old_state, new_state); } old_state } - /// created -> ready /// suspend -> ready /// /// # Errors /// if change state fails. pub(crate) fn ready(&self) -> std::io::Result<()> { let current = self.state(); - match current { - CoroutineState::Created => { + if let CoroutineState::Suspend(_, timestamp) = current { + if timestamp <= now() { let new_state = CoroutineState::Ready; let old_state = self.change_state(new_state); self.on_ready(self, old_state); return Ok(()); } - CoroutineState::Suspend(_, timestamp) => { - if timestamp <= open_coroutine_timer::now() { - let new_state = CoroutineState::Ready; - let old_state = self.change_state(new_state); - self.on_ready(self, old_state); - return Ok(()); - } - } - _ => {} } Err(Error::new( ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::Ready ), )) @@ -67,7 +55,6 @@ where /// syscall -> running /// /// below just for test - /// created -> running /// suspend -> running /// /// # Errors @@ -76,14 +63,7 @@ where let current = self.state(); match current { CoroutineState::Running => return Ok(()), - #[cfg(test)] - CoroutineState::Created => { - let new_state = CoroutineState::Running; - let old_state = self.change_state(new_state); - self.on_running(self, old_state); - return Ok(()); - } - CoroutineState::Ready => { + CoroutineState::Ready | CoroutineState::SystemCall(_, _, SyscallState::Executing) => { let new_state = CoroutineState::Running; let old_state = self.change_state(new_state); self.on_running(self, old_state); @@ -91,7 +71,7 @@ where } // #[cfg(test)] preemptive.rs use this CoroutineState::Suspend(_, timestamp) => { - if timestamp <= open_coroutine_timer::now() { + if timestamp <= now() { let new_state = CoroutineState::Running; let old_state = self.change_state(new_state); self.on_running(self, old_state); @@ -101,19 +81,13 @@ where CoroutineState::SystemCall(_, _, SyscallState::Callback | SyscallState::Timeout) => { return Ok(()); } - CoroutineState::SystemCall(_, _, SyscallState::Executing) => { - let new_state = CoroutineState::Running; - let old_state = self.change_state(new_state); - self.on_running(self, old_state); - return Ok(()); - } _ => {} } Err(Error::new( ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::Running ), )) @@ -135,7 +109,7 @@ where ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::Suspend(val, timestamp) ), )) @@ -174,7 +148,7 @@ where ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::SystemCall(val, syscall, syscall_state) ), )) @@ -196,7 +170,7 @@ where ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::Complete(val) ), )) @@ -218,7 +192,7 @@ where ErrorKind::Other, format!( "{} unexpected {current}->{:?}", - self.get_name(), + self.name(), CoroutineState::::Error(msg) ), )) diff --git a/open-coroutine-core/src/coroutine/suspender.rs b/core/src/coroutine/suspender.rs similarity index 78% rename from open-coroutine-core/src/coroutine/suspender.rs rename to core/src/coroutine/suspender.rs index b96c48ad..252ba982 100644 --- a/open-coroutine-core/src/coroutine/suspender.rs +++ b/core/src/coroutine/suspender.rs @@ -1,14 +1,14 @@ +use crate::common::get_timeout_time; use crate::impl_current_for; use std::cell::RefCell; use std::collections::VecDeque; -use std::panic::UnwindSafe; use std::time::Duration; thread_local! { static TIMESTAMP: RefCell> = const { RefCell::new(VecDeque::new()) }; } -impl<'s, Param: UnwindSafe, Yield: UnwindSafe> Suspender<'s, Param, Yield> { +impl Suspender<'_, Param, Yield> { /// Delay the execution of the coroutine with an arg after `Duration`. pub fn delay_with(&self, arg: Yield, delay: Duration) -> Param { self.until_with(arg, get_timeout_time(delay)) @@ -28,7 +28,7 @@ impl<'s, Param: UnwindSafe, Yield: UnwindSafe> Suspender<'s, Param, Yield> { } #[allow(clippy::must_use_candidate)] -impl<'s, Param: UnwindSafe> Suspender<'s, Param, ()> { +impl<'s, Param> Suspender<'s, Param, ()> { /// see the `suspend_with` documents. pub fn suspend(&self) -> Param { self.suspend_with(()) @@ -45,28 +45,25 @@ impl<'s, Param: UnwindSafe> Suspender<'s, Param, ()> { } } -impl_current_for!( - SUSPENDER, - Suspender<'s, Param: UnwindSafe, Yield: UnwindSafe> -); +impl_current_for!(SUSPENDER, Suspender<'s, Param, Yield>); #[cfg(feature = "korosensei")] pub use korosensei::Suspender; -use open_coroutine_timer::get_timeout_time; - #[cfg(feature = "korosensei")] mod korosensei { - use crate::common::Current; use corosensei::Yielder; - use std::panic::UnwindSafe; + use derivative::Derivative; /// Ths suspender implemented for coroutine. #[repr(C)] - pub struct Suspender<'s, Param: UnwindSafe, Yield: UnwindSafe> { + #[derive(Derivative)] + #[derivative(Debug)] + pub struct Suspender<'s, Param, Yield> { + #[derivative(Debug = "ignore")] inner: &'s Yielder, } - impl<'s, Param: UnwindSafe, Yield: UnwindSafe> Suspender<'s, Param, Yield> { + impl<'s, Param, Yield> Suspender<'s, Param, Yield> { pub(crate) fn new(inner: &'s Yielder) -> Self { Self { inner } } diff --git a/open-coroutine-core/src/lib.rs b/core/src/lib.rs similarity index 79% rename from open-coroutine-core/src/lib.rs rename to core/src/lib.rs index e4f16613..e7735265 100644 --- a/open-coroutine-core/src/lib.rs +++ b/core/src/lib.rs @@ -1,18 +1,25 @@ #![deny( // The following are allowed by default lints according to // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + absolute_paths_not_starting_with_crate, + explicit_outlives_requirements, + macro_use_extern_crate, + redundant_lifetimes, anonymous_parameters, bare_trait_objects, // elided_lifetimes_in_paths, // allow anonymous lifetime missing_copy_implementations, missing_debug_implementations, - // missing_docs, // TODO: add documents + missing_docs, // single_use_lifetimes, // TODO: fix lifetime names only used once // trivial_casts, trivial_numeric_casts, - // unreachable_pub, allow clippy::redundant_pub_crate lint instead + unreachable_pub, // unsafe_code, unstable_features, + // unused_crate_dependencies, + unused_lifetimes, + unused_macro_rules, unused_extern_crates, unused_import_braces, unused_qualifications, @@ -44,27 +51,32 @@ clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix clippy::single_char_lifetime_names, // TODO: change lifetime names )] -pub mod log; - -/// Constants. -pub mod constants; +//! see `https://github.com/acl-dev/open-coroutine` /// Common traits and impl. pub mod common; +/// Coroutine impls. pub mod coroutine; -#[cfg(all(unix, feature = "preemptive-schedule"))] +/// Make the coroutine automatically yield. +#[cfg(all(unix, feature = "preemptive"))] mod monitor; +/// Scheduler impls. pub mod scheduler; -pub mod pool; +/// Coroutine pool abstraction and impl. +pub mod co_pool; +/// net abstraction and impl. +#[allow(dead_code)] #[cfg(feature = "net")] pub mod net; +/// Syscall impl. #[allow( + missing_docs, clippy::similar_names, clippy::not_unsafe_ptr_arg_deref, clippy::many_single_char_names, diff --git a/core/src/monitor.rs b/core/src/monitor.rs new file mode 100644 index 00000000..855ac47b --- /dev/null +++ b/core/src/monitor.rs @@ -0,0 +1,242 @@ +use crate::common::beans::BeanFactory; +use crate::common::constants::{CoroutineState, MONITOR_BEAN}; +use crate::common::{get_timeout_time, now, CondvarBlocker}; +use crate::coroutine::listener::Listener; +use crate::coroutine::local::CoroutineLocal; +use crate::scheduler::SchedulableSuspender; +use crate::{catch, error, impl_current_for, impl_display_by_debug, info}; +use nix::sys::pthread::{pthread_kill, pthread_self, Pthread}; +use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; +use std::cell::{Cell, UnsafeCell}; +use std::collections::HashSet; +use std::fmt::Debug; +use std::io::{Error, ErrorKind}; +use std::mem::MaybeUninit; +use std::sync::Arc; +use std::thread::JoinHandle; +use std::time::Duration; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +struct NotifyNode { + timestamp: u64, + pthread: Pthread, +} + +/// Enums used to describe monitor state +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +enum MonitorState { + /// The monitor is created. + Created, + /// The monitor is running. + Running, + /// The monitor is stopping. + Stopping, + /// The monitor is stopped. + Stopped, +} + +impl_display_by_debug!(MonitorState); + +/// The monitor impls. +#[repr(C)] +#[derive(Debug)] +pub(crate) struct Monitor { + notify_queue: UnsafeCell>, + state: Cell, + thread: UnsafeCell>>, + blocker: Arc, +} + +impl Default for Monitor { + fn default() -> Self { + Monitor { + notify_queue: UnsafeCell::default(), + state: Cell::new(MonitorState::Created), + thread: UnsafeCell::new(MaybeUninit::uninit()), + blocker: Arc::default(), + } + } +} + +impl Monitor { + fn get_instance<'m>() -> &'m Self { + BeanFactory::get_or_default(MONITOR_BEAN) + } + + fn start(&self) -> std::io::Result<()> { + extern "C" fn sigurg_handler(_: libc::c_int) { + if let Ok(mut set) = SigSet::thread_get_mask() { + //删除对SIGURG信号的屏蔽,使信号处理函数即使在处理中,也可以再次进入信号处理函数 + set.remove(Signal::SIGURG); + set.thread_set_mask() + .expect("Failed to remove SIGURG signal mask!"); + if let Some(suspender) = SchedulableSuspender::current() { + suspender.suspend(); + } + } + } + match self.state.get() { + MonitorState::Created => { + self.state.set(MonitorState::Running); + // install SIGURG signal handler + let mut set = SigSet::empty(); + set.add(Signal::SIGURG); + let sa = SigAction::new( + SigHandler::Handler(sigurg_handler), + SaFlags::SA_RESTART, + set, + ); + unsafe { _ = sigaction(Signal::SIGURG, &sa)? }; + // start the monitor thread + let monitor = unsafe { &mut *self.thread.get() }; + *monitor = MaybeUninit::new( + std::thread::Builder::new() + .name("open-coroutine-monitor".to_string()) + .spawn(|| { + info!("monitor started !"); + if catch!( + Self::monitor_thread_main, + String::from("Monitor thread run failed without message"), + String::from("Monitor thread") + ) + .is_ok() + { + info!("monitor stopped !"); + } + })?, + ); + Ok(()) + } + MonitorState::Running => Ok(()), + MonitorState::Stopping | MonitorState::Stopped => Err(Error::new( + ErrorKind::Unsupported, + "Restart operation is unsupported !", + )), + } + } + + fn monitor_thread_main() { + let monitor = Self::get_instance(); + Self::init_current(monitor); + let notify_queue = unsafe { &*monitor.notify_queue.get() }; + while MonitorState::Running == monitor.state.get() || !notify_queue.is_empty() { + //只遍历,不删除,如果抢占调度失败,会在1ms后不断重试,相当于主动检测 + for node in notify_queue { + if now() < node.timestamp { + continue; + } + //实际上只对陷入重度计算的协程发送信号抢占 + //对于陷入执行系统调用的协程不发送信号(如果发送信号,会打断系统调用,进而降低总体性能) + if pthread_kill(node.pthread, Signal::SIGURG).is_err() { + error!( + "Attempt to preempt scheduling for thread:{} failed !", + node.pthread + ); + } + } + //monitor线程不执行协程计算任务,每次循环至少wait 1ms + monitor.blocker.clone().block(Duration::from_millis(1)); + } + Self::clean_current(); + assert_eq!( + MonitorState::Stopping, + monitor.state.replace(MonitorState::Stopped) + ); + } + + #[allow(dead_code)] + pub(crate) fn stop() { + Self::get_instance().state.set(MonitorState::Stopping); + } + + fn submit(timestamp: u64) -> std::io::Result { + let instance = Self::get_instance(); + instance.start()?; + let queue = unsafe { &mut *instance.notify_queue.get() }; + let node = NotifyNode { + timestamp, + pthread: pthread_self(), + }; + _ = queue.insert(node); + instance.blocker.notify(); + Ok(node) + } + + fn remove(node: &NotifyNode) -> bool { + let instance = Self::get_instance(); + let queue = unsafe { &mut *instance.notify_queue.get() }; + queue.remove(node) + } +} + +impl_current_for!(MONITOR, Monitor); + +#[repr(C)] +#[derive(Debug, Default)] +pub(crate) struct MonitorListener {} + +const NOTIFY_NODE: &str = "MONITOR_NODE"; + +impl Listener for MonitorListener { + fn on_state_changed( + &self, + local: &CoroutineLocal, + _: CoroutineState, + new_state: CoroutineState, + ) { + if Monitor::current().is_some() { + return; + } + match new_state { + CoroutineState::Ready => {} + CoroutineState::Running => { + let timestamp = get_timeout_time(Duration::from_millis(10)); + if let Ok(node) = Monitor::submit(timestamp) { + _ = local.put(NOTIFY_NODE, node); + } + } + CoroutineState::Suspend(_, _) + | CoroutineState::SystemCall(_, _, _) + | CoroutineState::Complete(_) + | CoroutineState::Error(_) => { + if let Some(node) = local.get(NOTIFY_NODE) { + _ = Monitor::remove(node); + } + } + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(not(target_arch = "riscv64"))] + #[test] + fn test() -> std::io::Result<()> { + use nix::sys::pthread::pthread_kill; + use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; + use std::os::unix::prelude::JoinHandleExt; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::time::Duration; + + static SIGNALED: AtomicBool = AtomicBool::new(false); + extern "C" fn handler(_: libc::c_int) { + SIGNALED.store(true, Ordering::Relaxed); + } + let mut set = SigSet::empty(); + set.add(Signal::SIGUSR1); + let sa = SigAction::new(SigHandler::Handler(handler), SaFlags::SA_RESTART, set); + unsafe { _ = sigaction(Signal::SIGUSR1, &sa)? }; + + SIGNALED.store(false, Ordering::Relaxed); + let handle = std::thread::spawn(|| { + std::thread::sleep(Duration::from_secs(2)); + }); + std::thread::sleep(Duration::from_secs(1)); + pthread_kill(handle.as_pthread_t(), Signal::SIGUSR1)?; + std::thread::sleep(Duration::from_secs(2)); + assert!(SIGNALED.load(Ordering::Relaxed)); + Ok(()) + } +} diff --git a/core/src/net/config.rs b/core/src/net/config.rs new file mode 100644 index 00000000..0efc0839 --- /dev/null +++ b/core/src/net/config.rs @@ -0,0 +1,114 @@ +use crate::common::constants::{cpu_count, DEFAULT_STACK_SIZE}; + +#[repr(C)] +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct Config { + event_loop_size: usize, + stack_size: usize, + min_size: usize, + max_size: usize, + keep_alive_time: u64, + hook: bool, +} + +impl Config { + #[must_use] + pub fn single() -> Self { + Self::new(1, DEFAULT_STACK_SIZE, 0, 65536, 0, true) + } + + #[must_use] + pub fn new( + event_loop_size: usize, + stack_size: usize, + min_size: usize, + max_size: usize, + keep_alive_time: u64, + hook: bool, + ) -> Self { + Self { + event_loop_size, + stack_size, + min_size, + max_size, + keep_alive_time, + hook, + } + } + + #[must_use] + pub fn event_loop_size(&self) -> usize { + self.event_loop_size + } + + #[must_use] + pub fn stack_size(&self) -> usize { + self.stack_size + } + + #[must_use] + pub fn min_size(&self) -> usize { + self.min_size + } + + #[must_use] + pub fn max_size(&self) -> usize { + self.max_size + } + + #[must_use] + pub fn keep_alive_time(&self) -> u64 { + self.keep_alive_time + } + + #[must_use] + pub fn hook(&self) -> bool { + self.hook + } + + pub fn set_event_loop_size(&mut self, event_loop_size: usize) -> &mut Self { + assert!( + event_loop_size > 0, + "event_loop_size must be greater than 0" + ); + self.event_loop_size = event_loop_size; + self + } + + pub fn set_stack_size(&mut self, stack_size: usize) -> &mut Self { + assert!(stack_size > 0, "stack_size must be greater than 0"); + self.stack_size = stack_size; + self + } + + pub fn set_min_size(&mut self, min_size: usize) -> &mut Self { + self.min_size = min_size; + self + } + + pub fn set_max_size(&mut self, max_size: usize) -> &mut Self { + assert!(max_size > 0, "max_size must be greater than 0"); + assert!( + max_size >= self.min_size, + "max_size must be greater than or equal to min_size" + ); + self.max_size = max_size; + self + } + + pub fn set_keep_alive_time(&mut self, keep_alive_time: u64) -> &mut Self { + self.keep_alive_time = keep_alive_time; + self + } + + pub fn set_hook(&mut self, hook: bool) -> &mut Self { + self.hook = hook; + self + } +} + +impl Default for Config { + fn default() -> Self { + Self::new(cpu_count(), DEFAULT_STACK_SIZE, 0, 65536, 0, true) + } +} diff --git a/core/src/net/event_loop.rs b/core/src/net/event_loop.rs new file mode 100644 index 00000000..98db60e7 --- /dev/null +++ b/core/src/net/event_loop.rs @@ -0,0 +1,479 @@ +use crate::co_pool::CoroutinePool; +use crate::common::beans::BeanFactory; +use crate::common::constants::{CoroutineState, PoolState, Syscall, SyscallState, SLICE}; +use crate::net::selector::{Event, Events, Poller, Selector}; +use crate::scheduler::SchedulableCoroutine; +use crate::{error, impl_current_for, impl_display_by_debug, info}; +use crossbeam_utils::atomic::AtomicCell; +use dashmap::DashSet; +use once_cell::sync::Lazy; +use rand::Rng; +use std::ffi::{c_char, c_int, c_void, CStr, CString}; +use std::io::{Error, ErrorKind}; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; +use std::time::Duration; + +cfg_if::cfg_if! { + if #[cfg(all(target_os = "linux", feature = "io_uring"))] { + use libc::{epoll_event, iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; + use dashmap::DashMap; + } +} + +#[repr(C)] +#[derive(Debug)] +pub(crate) struct EventLoop<'e> { + //状态 + state: AtomicCell, + stop: Arc<(Mutex, Condvar)>, + shared_stop: Arc<(Mutex, Condvar)>, + cpu: usize, + #[cfg(all(target_os = "linux", feature = "io_uring"))] + operator: crate::net::operator::Operator<'e>, + #[allow(clippy::type_complexity)] + #[cfg(all(target_os = "linux", feature = "io_uring"))] + syscall_wait_table: DashMap>, Condvar)>>, + selector: Poller, + pool: CoroutinePool<'e>, + phantom_data: PhantomData<&'e EventLoop<'e>>, +} + +impl<'e> Deref for EventLoop<'e> { + type Target = CoroutinePool<'e>; + + fn deref(&self) -> &Self::Target { + &self.pool + } +} + +impl DerefMut for EventLoop<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.pool + } +} + +impl Default for EventLoop<'_> { + fn default() -> Self { + let max_cpu_index = num_cpus::get(); + let random_cpu_index = rand::thread_rng().gen_range(0..max_cpu_index); + Self::new( + format!("open-coroutine-event-loop-{random_cpu_index}"), + random_cpu_index, + crate::common::constants::DEFAULT_STACK_SIZE, + 0, + 65536, + 0, + Arc::new((Mutex::new(AtomicUsize::new(0)), Condvar::new())), + ) + .expect("create event-loop failed") + } +} + +static COROUTINE_TOKENS: Lazy> = Lazy::new(DashSet::new); + +impl<'e> EventLoop<'e> { + pub(super) fn new( + name: String, + cpu: usize, + stack_size: usize, + min_size: usize, + max_size: usize, + keep_alive_time: u64, + shared_stop: Arc<(Mutex, Condvar)>, + ) -> std::io::Result { + Ok(EventLoop { + state: AtomicCell::new(PoolState::Running), + stop: Arc::new((Mutex::new(false), Condvar::new())), + shared_stop, + cpu, + #[cfg(all(target_os = "linux", feature = "io_uring"))] + operator: crate::net::operator::Operator::new(cpu)?, + #[cfg(all(target_os = "linux", feature = "io_uring"))] + syscall_wait_table: DashMap::new(), + selector: Poller::new()?, + pool: CoroutinePool::new(name, stack_size, min_size, max_size, keep_alive_time), + phantom_data: PhantomData, + }) + } + + #[allow(trivial_numeric_casts, clippy::cast_possible_truncation)] + fn token(syscall: Syscall) -> usize { + if let Some(co) = SchedulableCoroutine::current() { + let boxed: &'static mut CString = Box::leak(Box::from( + CString::new(co.name()).expect("build name failed!"), + )); + let cstr: &'static CStr = boxed.as_c_str(); + let token = cstr.as_ptr().cast::() as usize; + assert!(COROUTINE_TOKENS.insert(token)); + return token; + } + unsafe { + cfg_if::cfg_if! { + if #[cfg(windows)] { + let thread_id = windows_sys::Win32::System::Threading::GetCurrentThread(); + } else { + let thread_id = libc::pthread_self(); + } + } + let syscall_mask = >::into(syscall).as_ptr() as usize; + let token = thread_id as usize ^ syscall_mask; + if Syscall::nio() != syscall { + eprintln!("{syscall} {token}"); + } + token + } + } + + pub(super) fn add_read_event(&self, fd: c_int) -> std::io::Result<()> { + self.selector + .add_read_event(fd, EventLoop::token(Syscall::nio())) + } + + pub(super) fn add_write_event(&self, fd: c_int) -> std::io::Result<()> { + self.selector + .add_write_event(fd, EventLoop::token(Syscall::nio())) + } + + pub(super) fn del_event(&self, fd: c_int) -> std::io::Result<()> { + self.selector.del_event(fd) + } + + pub(super) fn del_read_event(&self, fd: c_int) -> std::io::Result<()> { + self.selector.del_read_event(fd) + } + + pub(super) fn del_write_event(&self, fd: c_int) -> std::io::Result<()> { + self.selector.del_write_event(fd) + } + + pub(super) fn wait_event(&mut self, timeout: Option) -> std::io::Result<()> { + let left_time = if SchedulableCoroutine::current().is_some() { + timeout + } else if let Some(time) = timeout { + Some( + self.try_timed_schedule_task(time) + .map(Duration::from_nanos)?, + ) + } else { + self.try_schedule_task()?; + None + }; + self.wait_just(left_time) + } + + /// Wait events happen. + pub(super) fn timed_wait_just(&self, timeout: Option) -> std::io::Result<()> { + let timeout_time = timeout.map_or(u64::MAX, crate::common::get_timeout_time); + loop { + let left_time = timeout_time + .saturating_sub(crate::common::now()) + .min(10_000_000); + if left_time == 0 { + //timeout + return self.wait_just(Some(Duration::ZERO)); + } + self.wait_just(Some(Duration::from_nanos(left_time)))?; + } + } + + pub(super) fn wait_just(&self, timeout: Option) -> std::io::Result<()> { + let mut left_time = timeout; + if let Some(time) = left_time { + let timestamp = crate::common::get_timeout_time(time); + if let Some(co) = SchedulableCoroutine::current() { + if let CoroutineState::SystemCall((), syscall, SyscallState::Executing) = co.state() + { + let new_state = SyscallState::Suspend(timestamp); + if co.syscall((), syscall, new_state).is_err() { + error!( + "{} change to syscall {} {} failed !", + co.name(), + syscall, + new_state + ); + } + } + } + if let Some(suspender) = crate::scheduler::SchedulableSuspender::current() { + suspender.until(timestamp); + //回来的时候等待的时间已经到了 + left_time = Some(Duration::ZERO); + } + if let Some(co) = SchedulableCoroutine::current() { + if let CoroutineState::SystemCall( + (), + syscall, + SyscallState::Callback | SyscallState::Timeout, + ) = co.state() + { + let new_state = SyscallState::Executing; + if co.syscall((), syscall, new_state).is_err() { + error!( + "{} change to syscall {} {} failed !", + co.name(), + syscall, + new_state + ); + } + } + } + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + if crate::net::operator::support_io_uring() { + // use io_uring + let (count, mut cq, left) = self.operator.select(left_time, 0)?; + if count > 0 { + for cqe in &mut cq { + let token = usize::try_from(cqe.user_data()).expect("token overflow"); + if crate::common::constants::IO_URING_TIMEOUT_USERDATA == token { + continue; + } + // resolve completed read/write tasks + let result = cqe.result() as ssize_t; + eprintln!("io_uring finish {token} {result}"); + if let Some((_, pair)) = self.syscall_wait_table.remove(&token) { + let (lock, cvar) = &*pair; + let mut pending = lock.lock().expect("lock failed"); + *pending = Some(result); + cvar.notify_one(); + } + unsafe { self.resume(token) }; + } + } + if left != left_time { + left_time = Some(left.unwrap_or(Duration::ZERO)); + } + } + + // use epoll/kevent/iocp + let mut events = Events::with_capacity(1024); + self.selector.select(&mut events, left_time)?; + #[allow(clippy::explicit_iter_loop)] + for event in events.iter() { + let token = event.get_token(); + if event.readable() || event.writable() { + unsafe { self.resume(token) }; + } + } + Ok(()) + } + + #[allow(clippy::unused_self)] + unsafe fn resume(&self, token: usize) { + if COROUTINE_TOKENS.remove(&token).is_none() { + return; + } + if let Ok(co_name) = CStr::from_ptr((token as *const c_void).cast::()).to_str() { + self.try_resume(co_name); + } + } + + pub(super) fn start(self) -> std::io::Result> + where + 'e: 'static, + { + // init stop flag + { + let (lock, cvar) = &*self.stop; + let mut pending = lock.lock().expect("lock failed"); + *pending = true; + cvar.notify_one(); + } + let thread_name = self.get_thread_name(); + let bean_name = self.name().to_string().leak(); + let bean_name_in_thread = self.name().to_string().leak(); + BeanFactory::init_bean(bean_name, self); + BeanFactory::init_bean( + &thread_name, + std::thread::Builder::new() + .name(thread_name.clone()) + .spawn(move || { + let consumer = + unsafe { BeanFactory::get_mut_bean::(bean_name_in_thread) } + .unwrap_or_else(|| panic!("bean {bean_name_in_thread} not exist !")); + { + let (lock, cvar) = &*consumer.shared_stop.clone(); + let started = lock.lock().expect("lock failed"); + _ = started.fetch_add(1, Ordering::Release); + cvar.notify_one(); + } + // thread per core + info!( + "{} has started, bind to CPU:{}", + consumer.name(), + core_affinity::set_for_current(core_affinity::CoreId { id: consumer.cpu }) + ); + Self::init_current(consumer); + while PoolState::Running == consumer.state() + || !consumer.is_empty() + || consumer.get_running_size() > 0 + { + _ = consumer.wait_event(Some(SLICE)); + } + // notify stop flags + { + let (lock, cvar) = &*consumer.stop.clone(); + let mut pending = lock.lock().expect("lock failed"); + *pending = false; + cvar.notify_one(); + } + { + let (lock, cvar) = &*consumer.shared_stop.clone(); + let started = lock.lock().expect("lock failed"); + _ = started.fetch_sub(1, Ordering::Release); + cvar.notify_one(); + } + Self::clean_current(); + info!("{} has exited", consumer.name()); + })?, + ); + unsafe { + Ok(Arc::from_raw( + BeanFactory::get_bean::(bean_name) + .unwrap_or_else(|| panic!("bean {bean_name} not exist !")), + )) + } + } + + fn get_thread_name(&self) -> String { + format!("{}-thread", self.name()) + } + + pub(super) fn stop_sync(&mut self, wait_time: Duration) -> std::io::Result<()> { + match self.state() { + PoolState::Running => { + assert_eq!(PoolState::Running, self.stopping()?); + let timeout_time = crate::common::get_timeout_time(wait_time); + loop { + let left_time = timeout_time.saturating_sub(crate::common::now()); + if 0 == left_time { + return Err(Error::new(ErrorKind::TimedOut, "stop timeout !")); + } + self.wait_event(Some(Duration::from_nanos(left_time).min(SLICE)))?; + if self.is_empty() && self.get_running_size() == 0 { + assert_eq!(PoolState::Stopping, self.stopped()?); + return Ok(()); + } + } + } + PoolState::Stopping => Err(Error::new(ErrorKind::Other, "should never happens")), + PoolState::Stopped => Ok(()), + } + } + + pub(super) fn stop(&self, wait_time: Duration) -> std::io::Result<()> { + match self.state() { + PoolState::Running => { + if BeanFactory::remove_bean::>(&self.get_thread_name()).is_some() { + assert_eq!(PoolState::Running, self.stopping()?); + //开启了单独的线程 + let (lock, cvar) = &*self.stop; + let result = cvar + .wait_timeout_while( + lock.lock().expect("lock failed"), + wait_time, + |&mut pending| pending, + ) + .expect("lock failed"); + if result.1.timed_out() { + return Err(Error::new(ErrorKind::TimedOut, "stop timeout !")); + } + assert_eq!(PoolState::Stopping, self.stopped()?); + } + Ok(()) + } + PoolState::Stopping => Err(Error::new(ErrorKind::Other, "should never happens")), + PoolState::Stopped => Ok(()), + } + } +} + +impl_current_for!(EVENT_LOOP, EventLoop<'e>); + +impl_display_by_debug!(EventLoop<'e>); + +macro_rules! impl_io_uring { + ( $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[cfg(all(target_os = "linux", feature = "io_uring"))] + impl EventLoop<'_> { + pub(super) fn $syscall( + &self, + $($arg: $arg_type),* + ) -> std::io::Result>, Condvar)>> { + let token = EventLoop::token(Syscall::$syscall); + self.operator.$syscall(token, $($arg, )*)?; + let arc = Arc::new((Mutex::new(None), Condvar::new())); + assert!( + self.syscall_wait_table.insert(token, arc.clone()).is_none(), + "The previous token was not retrieved in a timely manner" + ); + Ok(arc) + } + } + } +} + +impl_io_uring!(epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int); +impl_io_uring!(socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int); +impl_io_uring!(accept(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t) -> c_int); +impl_io_uring!(accept4(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t, flg: c_int) -> c_int); +impl_io_uring!(shutdown(fd: c_int, how: c_int) -> c_int); +impl_io_uring!(connect(fd: c_int, address: *const sockaddr, len: socklen_t) -> c_int); +impl_io_uring!(close(fd: c_int) -> c_int); +impl_io_uring!(recv(fd: c_int, buf: *mut c_void, len: size_t, flags: c_int) -> ssize_t); +impl_io_uring!(read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t); +impl_io_uring!(pread(fd: c_int, buf: *mut c_void, count: size_t, offset: off_t) -> ssize_t); +impl_io_uring!(readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_io_uring!(preadv(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_io_uring!(recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t); +impl_io_uring!(send(fd: c_int, buf: *const c_void, len: size_t, flags: c_int) -> ssize_t); +impl_io_uring!(sendto(fd: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t); +impl_io_uring!(write(fd: c_int, buf: *const c_void, count: size_t) -> ssize_t); +impl_io_uring!(pwrite(fd: c_int, buf: *const c_void, count: size_t, offset: off_t) -> ssize_t); +impl_io_uring!(writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_io_uring!(pwritev(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_io_uring!(sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t); + +#[cfg(all(test, not(all(unix, feature = "preemptive"))))] +mod tests { + use crate::net::event_loop::EventLoop; + use std::time::Duration; + + #[test] + fn test_simple() -> std::io::Result<()> { + let mut event_loop = EventLoop::default(); + event_loop.set_max_size(1); + _ = event_loop.submit_task(None, |_| panic!("test panic, just ignore it"), None)?; + _ = event_loop.submit_task( + None, + |_| { + println!("2"); + Some(2) + }, + None, + )?; + event_loop.stop_sync(Duration::from_secs(3)) + } + + #[ignore] + #[test] + fn test_simple_auto() -> std::io::Result<()> { + let event_loop = EventLoop::default().start()?; + event_loop.set_max_size(1); + _ = event_loop.submit_task(None, |_| panic!("test panic, just ignore it"), None)?; + _ = event_loop.submit_task( + None, + |_| { + println!("2"); + Some(2) + }, + None, + )?; + event_loop.stop(Duration::from_secs(3)) + } +} diff --git a/core/src/net/join.rs b/core/src/net/join.rs new file mode 100644 index 00000000..5a46a4b6 --- /dev/null +++ b/core/src/net/join.rs @@ -0,0 +1,70 @@ +use crate::net::event_loop::EventLoop; +use std::ffi::{c_char, CStr, CString}; +use std::io::{Error, ErrorKind}; +use std::sync::Arc; +use std::time::Duration; + +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug)] +pub struct JoinHandle(&'static Arc>, *const c_char); + +impl JoinHandle { + /// create `JoinHandle` instance. + pub(crate) fn err(pool: &'static Arc>) -> Self { + Self::new(pool, "") + } + + /// create `JoinHandle` instance. + pub(crate) fn new(pool: &'static Arc>, name: &str) -> Self { + let boxed: &'static mut CString = Box::leak(Box::from( + CString::new(name).expect("init JoinHandle failed!"), + )); + let cstr: &'static CStr = boxed.as_c_str(); + JoinHandle(pool, cstr.as_ptr()) + } + + /// get the task name. + /// + /// # Errors + /// if the task name is invalid. + pub fn get_name(&self) -> std::io::Result<&str> { + unsafe { CStr::from_ptr(self.1) } + .to_str() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid task name")) + } + + /// join with `Duration`. + /// + /// # Errors + /// see `timeout_at_join`. + pub fn timeout_join(&self, dur: Duration) -> std::io::Result, &str>> { + self.timeout_at_join(crate::common::get_timeout_time(dur)) + } + + /// join. + /// + /// # Errors + /// see `timeout_at_join`. + pub fn join(&self) -> std::io::Result, &str>> { + self.timeout_at_join(u64::MAX) + } + + /// join with timeout. + /// + /// # Errors + /// if join failed. + pub fn timeout_at_join( + &self, + timeout_time: u64, + ) -> std::io::Result, &str>> { + let name = self.get_name()?; + if name.is_empty() { + return Err(Error::new(ErrorKind::InvalidInput, "Invalid task name")); + } + self.0.wait_task_result( + name, + Duration::from_nanos(timeout_time.saturating_sub(crate::common::now())), + ) + } +} diff --git a/core/src/net/mod.rs b/core/src/net/mod.rs new file mode 100644 index 00000000..c8b73e69 --- /dev/null +++ b/core/src/net/mod.rs @@ -0,0 +1,276 @@ +use crate::coroutine::suspender::Suspender; +use crate::net::config::Config; +use crate::net::event_loop::EventLoop; +use crate::net::join::JoinHandle; +use crate::{error, info}; +use once_cell::sync::OnceCell; +use std::collections::VecDeque; +use std::ffi::c_int; +use std::io::{Error, ErrorKind}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +/// 做C兼容时会用到 +pub type UserFunc = extern "C" fn(usize) -> usize; + +cfg_if::cfg_if! { + if #[cfg(all(target_os = "linux", feature = "io_uring"))] { + use libc::{epoll_event, iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; + use std::ffi::c_void; + } +} + +mod selector; + +#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] +#[cfg(all(target_os = "linux", feature = "io_uring"))] +mod operator; + +#[allow(missing_docs)] +pub mod event_loop; + +/// Configuration for `EventLoops`. +#[allow(missing_docs)] +pub mod config; + +/// Task join abstraction and impl. +pub mod join; + +static INSTANCE: OnceCell = OnceCell::new(); + +/// The manager for `EventLoop`. +#[repr(C)] +#[derive(Debug)] +pub struct EventLoops { + index: AtomicUsize, + loops: VecDeque>>, + shared_stop: Arc<(Mutex, Condvar)>, +} + +unsafe impl Send for EventLoops {} + +unsafe impl Sync for EventLoops {} + +impl EventLoops { + /// Init the `EventLoops`. + pub fn init(config: &Config) { + _ = INSTANCE.get_or_init(|| { + let loops = Self::new( + config.event_loop_size(), + config.stack_size(), + config.min_size(), + config.max_size(), + config.keep_alive_time(), + ) + .expect("init default EventLoops failed !"); + #[cfg(feature = "log")] + let _ = tracing_subscriber::fmt() + .with_thread_names(true) + .with_line_number(true) + .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( + time::UtcOffset::from_hms(8, 0, 0).expect("create UtcOffset failed !"), + time::format_description::well_known::Rfc2822, + )) + .try_init(); + info!("open-coroutine init with {config:#?}"); + loops + }); + } + + /// Create a new `EventLoops`. + pub fn new( + event_loop_size: usize, + stack_size: usize, + min_size: usize, + max_size: usize, + keep_alive_time: u64, + ) -> std::io::Result { + let shared_stop = Arc::new((Mutex::new(AtomicUsize::new(0)), Condvar::new())); + let mut loops = VecDeque::new(); + for i in 0..event_loop_size { + loops.push_back( + EventLoop::new( + format!("open-coroutine-event-loop-{i}"), + i, + stack_size, + min_size, + max_size, + keep_alive_time, + shared_stop.clone(), + )? + .start()?, + ); + } + Ok(Self { + index: AtomicUsize::new(0), + loops, + shared_stop, + }) + } + + fn round_robin() -> &'static Arc> { + let instance = INSTANCE.get().expect("EventLoops not init !"); + let index = instance.index.fetch_add(1, Ordering::Release) % instance.loops.len(); + instance + .loops + .get(index) + .unwrap_or_else(move || panic!("init event-loop-{index} failed!")) + } + + /// Get a `EventLoop`, prefer current. + fn event_loop() -> &'static EventLoop<'static> { + EventLoop::current().unwrap_or_else(|| Self::round_robin()) + } + + /// Submit a new task to event-loop. + /// + /// Allow multiple threads to concurrently submit task to the pool, + /// but only allow one thread to execute scheduling. + pub fn submit_task( + name: Option, + func: impl FnOnce(Option) -> Option + 'static, + param: Option, + ) -> JoinHandle { + let event_loop = Self::round_robin(); + event_loop.submit_task(name, func, param).map_or_else( + |_| JoinHandle::err(event_loop), + |n| JoinHandle::new(event_loop, n.as_str()), + ) + } + + /// Submit a new coroutine to event-loop. + /// + /// Allow multiple threads to concurrently submit coroutine to the pool, + /// but only allow one thread to execute scheduling. + pub fn submit_co( + f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + 'static, + stack_size: Option, + ) -> std::io::Result<()> { + Self::round_robin().submit_co(f, stack_size) + } + + /// Waiting for read or write events to occur. + /// This method can only be used in coroutines. + pub fn wait_event(timeout: Option) -> std::io::Result<()> { + Self::event_loop().timed_wait_just(timeout) + } + + /// Waiting for a read event to occur. + /// This method can only be used in coroutines. + pub fn wait_read_event(fd: c_int, timeout: Option) -> std::io::Result<()> { + let event_loop = Self::event_loop(); + event_loop.add_read_event(fd)?; + event_loop.wait_just(timeout) + } + + /// Waiting for a write event to occur. + /// This method can only be used in coroutines. + pub fn wait_write_event(fd: c_int, timeout: Option) -> std::io::Result<()> { + let event_loop = Self::event_loop(); + event_loop.add_write_event(fd)?; + event_loop.wait_just(timeout) + } + + /// Remove read and write event interests. + /// This method can only be used in coroutines. + pub fn del_event(fd: c_int) -> std::io::Result<()> { + if let Some(event_loop) = EventLoop::current() { + event_loop.del_event(fd)?; + } else { + let instance = INSTANCE.get().expect("EventLoops not init !"); + for event_loop in &instance.loops { + event_loop.del_event(fd)?; + } + } + Ok(()) + } + + /// Remove read event interest. + /// This method can only be used in coroutines. + pub fn del_read_event(fd: c_int) -> std::io::Result<()> { + if let Some(event_loop) = EventLoop::current() { + event_loop.del_read_event(fd)?; + } else { + let instance = INSTANCE.get().expect("EventLoops not init !"); + for event_loop in &instance.loops { + event_loop.del_read_event(fd)?; + } + } + Ok(()) + } + + /// Remove write event interest. + /// This method can only be used in coroutines. + pub fn del_write_event(fd: c_int) -> std::io::Result<()> { + if let Some(event_loop) = EventLoop::current() { + event_loop.del_write_event(fd)?; + } else { + let instance = INSTANCE.get().expect("EventLoops not init !"); + for event_loop in &instance.loops { + event_loop.del_write_event(fd)?; + } + } + Ok(()) + } + + /// Stop all `EventLoop`. + pub fn stop(wait_time: Duration) -> std::io::Result<()> { + if let Some(instance) = INSTANCE.get() { + for i in &instance.loops { + _ = i.stop(Duration::ZERO); + } + let (lock, cvar) = &*instance.shared_stop; + let guard = lock + .lock() + .map_err(|_| Error::new(ErrorKind::TimedOut, "wait failed !"))?; + let result = cvar + .wait_timeout_while(guard, wait_time, |stopped| { + stopped.load(Ordering::Acquire) > 0 + }) + .map_err(|_| Error::new(ErrorKind::TimedOut, "wait failed !"))?; + if result.1.timed_out() { + error!("open-coroutine stop timeout !"); + return Err(Error::new(ErrorKind::TimedOut, "stop timeout !")); + } + #[cfg(all(unix, feature = "preemptive"))] + crate::monitor::Monitor::stop(); + } + Ok(()) + } +} + +macro_rules! impl_io_uring { + ( $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[cfg(all(target_os = "linux", feature = "io_uring"))] + impl EventLoops { + #[allow(missing_docs)] + pub fn $syscall( + $($arg: $arg_type),* + ) -> std::io::Result>, Condvar)>> { + Self::event_loop().$syscall($($arg, )*) + } + } + } +} + +impl_io_uring!(epoll_ctl(epfd: c_int, op: c_int, fd: c_int, event: *mut epoll_event) -> c_int); +impl_io_uring!(socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int); +impl_io_uring!(accept(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t) -> c_int); +impl_io_uring!(accept4(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t, flg: c_int) -> c_int); +impl_io_uring!(shutdown(fd: c_int, how: c_int) -> c_int); +impl_io_uring!(connect(fd: c_int, address: *const sockaddr, len: socklen_t) -> c_int); +impl_io_uring!(close(fd: c_int) -> c_int); +impl_io_uring!(recv(fd: c_int, buf: *mut c_void, len: size_t, flags: c_int) -> ssize_t); +impl_io_uring!(read(fd: c_int, buf: *mut c_void, count: size_t) -> ssize_t); +impl_io_uring!(pread(fd: c_int, buf: *mut c_void, count: size_t, offset: off_t) -> ssize_t); +impl_io_uring!(readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_io_uring!(preadv(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_io_uring!(recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t); +impl_io_uring!(send(fd: c_int, buf: *const c_void, len: size_t, flags: c_int) -> ssize_t); +impl_io_uring!(sendto(fd: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t); +impl_io_uring!(write(fd: c_int, buf: *const c_void, count: size_t) -> ssize_t); +impl_io_uring!(pwrite(fd: c_int, buf: *const c_void, count: size_t, offset: off_t) -> ssize_t); +impl_io_uring!(writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_io_uring!(pwritev(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_io_uring!(sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t); diff --git a/core/src/net/operator/mod.rs b/core/src/net/operator/mod.rs new file mode 100644 index 00000000..b6dc89e4 --- /dev/null +++ b/core/src/net/operator/mod.rs @@ -0,0 +1,691 @@ +use derivative::Derivative; +use io_uring::opcode::{ + Accept, AsyncCancel, Close, Connect, EpollCtl, Fsync, MkDirAt, OpenAt, PollAdd, PollRemove, + Read, Readv, Recv, RecvMsg, RenameAt, Send, SendMsg, SendZc, Shutdown, Socket, Timeout, + TimeoutRemove, TimeoutUpdate, Write, Writev, +}; +use io_uring::squeue::Entry; +use io_uring::types::{epoll_event, Fd, Timespec}; +use io_uring::{CompletionQueue, IoUring, Probe}; +use libc::{ + c_char, c_int, c_uint, c_void, iovec, mode_t, msghdr, off_t, size_t, sockaddr, socklen_t, EBUSY, +}; +use once_cell::sync::Lazy; +use std::collections::VecDeque; +use std::io::{Error, ErrorKind}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; +use std::time::{Duration, Instant}; + +#[cfg(test)] +mod tests; + +static SUPPORT: Lazy = + Lazy::new(|| crate::common::current_kernel_version() >= crate::common::kernel_version(5, 6, 0)); + +#[must_use] +pub(crate) fn support_io_uring() -> bool { + *SUPPORT +} + +static PROBE: Lazy = Lazy::new(|| { + let mut probe = Probe::new(); + if let Ok(io_uring) = IoUring::new(2) { + if let Ok(()) = io_uring.submitter().register_probe(&mut probe) { + return probe; + } + } + panic!("probe init failed !") +}); + +// check https://www.rustwiki.org.cn/en/reference/introduction.html for help information +macro_rules! support { + ( $self:ident, $struct_name:ident, $opcode:ident, $impls:expr ) => { + return { + static $struct_name: Lazy = once_cell::sync::Lazy::new(|| { + if $crate::net::operator::support_io_uring() { + return PROBE.is_supported($opcode::CODE); + } + false + }); + if *$struct_name { + return $self.push_sq($impls); + } + Err(Error::new(ErrorKind::Unsupported, "unsupported")) + } + }; +} + +#[repr(C)] +#[derive(Derivative)] +#[derivative(Debug)] +pub(crate) struct Operator<'o> { + #[derivative(Debug = "ignore")] + inner: IoUring, + entering: AtomicBool, + backlog: Mutex>, +} + +impl Operator<'_> { + pub(crate) fn new(cpu: usize) -> std::io::Result { + IoUring::builder() + .setup_sqpoll(1000) + .setup_sqpoll_cpu(u32::try_from(cpu).unwrap_or(u32::MAX)) + .build(1024) + .map(|inner| Operator { + inner, + entering: AtomicBool::new(false), + backlog: Mutex::new(VecDeque::new()), + }) + } + + fn push_sq(&self, entry: Entry) -> std::io::Result<()> { + let entry = Box::leak(Box::new(entry)); + if unsafe { self.inner.submission_shared().push(entry).is_err() } { + self.backlog + .lock() + .expect("backlog lock failed") + .push_back(entry); + } + self.inner.submit().map(|_| ()) + } + + pub(crate) fn select( + &self, + timeout: Option, + want: usize, + ) -> std::io::Result<(usize, CompletionQueue, Option)> { + if support_io_uring() { + if self + .entering + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + return Ok((0, unsafe { self.inner.completion_shared() }, timeout)); + } + let result = self.do_select(timeout, want); + self.entering.store(false, Ordering::Release); + return result; + } + Err(Error::new(ErrorKind::Unsupported, "unsupported")) + } + + fn do_select( + &self, + timeout: Option, + want: usize, + ) -> std::io::Result<(usize, CompletionQueue, Option)> { + let start_time = Instant::now(); + self.timeout_add(crate::common::constants::IO_URING_TIMEOUT_USERDATA, timeout)?; + let mut cq = unsafe { self.inner.completion_shared() }; + // when submit queue is empty, submit_and_wait will block + let count = match self.inner.submit_and_wait(want) { + Ok(count) => count, + Err(err) => { + if err.raw_os_error() == Some(EBUSY) { + 0 + } else { + return Err(err); + } + } + }; + cq.sync(); + + // clean backlog + let mut sq = unsafe { self.inner.submission_shared() }; + loop { + if sq.is_full() { + match self.inner.submit() { + Ok(_) => (), + Err(err) => { + if err.raw_os_error() == Some(EBUSY) { + break; + } + return Err(err); + } + } + } + sq.sync(); + + let mut backlog = self.backlog.lock().expect("backlog lock failed"); + match backlog.pop_front() { + Some(sqe) => { + if unsafe { sq.push(sqe).is_err() } { + backlog.push_front(sqe); + break; + } + } + None => break, + } + } + let cost = Instant::now().saturating_duration_since(start_time); + Ok((count, cq, timeout.map(|t| t.saturating_sub(cost)))) + } + + pub(crate) fn async_cancel(&self, user_data: usize) -> std::io::Result<()> { + support!( + self, + SUPPORT_ASYNC_CANCEL, + AsyncCancel, + AsyncCancel::new(user_data as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn epoll_ctl( + &self, + user_data: usize, + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut libc::epoll_event, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_EPOLL_CTL, + EpollCtl, + EpollCtl::new( + Fd(epfd), + Fd(fd), + op, + event.cast_const().cast::(), + ) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn poll_add( + &self, + user_data: usize, + fd: c_int, + flags: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_POLL_ADD, + PollAdd, + PollAdd::new(Fd(fd), flags as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn poll_remove(&self, user_data: usize) -> std::io::Result<()> { + support!( + self, + SUPPORT_POLL_REMOVE, + PollRemove, + PollRemove::new(user_data as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn timeout_add( + &self, + user_data: usize, + timeout: Option, + ) -> std::io::Result<()> { + if let Some(duration) = timeout { + let timeout = Timespec::new() + .sec(duration.as_secs()) + .nsec(duration.subsec_nanos()); + support!( + self, + SUPPORT_TIMEOUT_ADD, + Timeout, + Timeout::new(&timeout).build().user_data(user_data as u64) + ) + } + Ok(()) + } + + pub(crate) fn timeout_update( + &self, + user_data: usize, + timeout: Option, + ) -> std::io::Result<()> { + if let Some(duration) = timeout { + let timeout = Timespec::new() + .sec(duration.as_secs()) + .nsec(duration.subsec_nanos()); + support!( + self, + SUPPORT_TIMEOUT_UPDATE, + TimeoutUpdate, + TimeoutUpdate::new(user_data as u64, &timeout) + .build() + .user_data(user_data as u64) + ) + } + self.timeout_remove(user_data) + } + + pub(crate) fn timeout_remove(&self, user_data: usize) -> std::io::Result<()> { + support!( + self, + SUPPORT_TIMEOUT_REMOVE, + TimeoutRemove, + TimeoutRemove::new(user_data as u64).build() + ) + } + + pub(crate) fn openat( + &self, + user_data: usize, + dir_fd: c_int, + pathname: *const c_char, + flags: c_int, + mode: mode_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_OPENAT, + OpenAt, + OpenAt::new(Fd(dir_fd), pathname) + .flags(flags) + .mode(mode) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn mkdirat( + &self, + user_data: usize, + dir_fd: c_int, + pathname: *const c_char, + mode: mode_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_MK_DIR_AT, + MkDirAt, + MkDirAt::new(Fd(dir_fd), pathname) + .mode(mode) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn renameat( + &self, + user_data: usize, + old_dir_fd: c_int, + old_path: *const c_char, + new_dir_fd: c_int, + new_path: *const c_char, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_RENAME_AT, + RenameAt, + RenameAt::new(Fd(old_dir_fd), old_path, Fd(new_dir_fd), new_path) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn renameat2( + &self, + user_data: usize, + old_dir_fd: c_int, + old_path: *const c_char, + new_dir_fd: c_int, + new_path: *const c_char, + flags: c_uint, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_RENAME_AT, + RenameAt, + RenameAt::new(Fd(old_dir_fd), old_path, Fd(new_dir_fd), new_path) + .flags(flags) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn fsync(&self, user_data: usize, fd: c_int) -> std::io::Result<()> { + support!( + self, + SUPPORT_FSYNC, + Fsync, + Fsync::new(Fd(fd)).build().user_data(user_data as u64) + ) + } + + pub(crate) fn socket( + &self, + user_data: usize, + domain: c_int, + ty: c_int, + protocol: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_SOCKET, + Socket, + Socket::new(domain, ty, protocol) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn accept( + &self, + user_data: usize, + fd: c_int, + address: *mut sockaddr, + address_len: *mut socklen_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_ACCEPT, + Accept, + Accept::new(Fd(fd), address, address_len) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn accept4( + &self, + user_data: usize, + fd: c_int, + addr: *mut sockaddr, + len: *mut socklen_t, + flg: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_ACCEPT, + Accept, + Accept::new(Fd(fd), addr, len) + .flags(flg) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn connect( + &self, + user_data: usize, + fd: c_int, + address: *const sockaddr, + len: socklen_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_CONNECT, + Connect, + Connect::new(Fd(fd), address, len) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn shutdown(&self, user_data: usize, fd: c_int, how: c_int) -> std::io::Result<()> { + support!( + self, + SUPPORT_SHUTDOWN, + Shutdown, + Shutdown::new(Fd(fd), how) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn close(&self, user_data: usize, fd: c_int) -> std::io::Result<()> { + support!( + self, + SUPPORT_CLOSE, + Close, + Close::new(Fd(fd)).build().user_data(user_data as u64) + ) + } + + pub(crate) fn recv( + &self, + user_data: usize, + fd: c_int, + buf: *mut c_void, + len: size_t, + flags: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_RECV, + Recv, + Recv::new(Fd(fd), buf.cast::(), len as u32) + .flags(flags) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn read( + &self, + user_data: usize, + fd: c_int, + buf: *mut c_void, + count: size_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_READ, + Read, + Read::new(Fd(fd), buf.cast::(), count as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn pread( + &self, + user_data: usize, + fd: c_int, + buf: *mut c_void, + count: size_t, + offset: off_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_READ, + Read, + Read::new(Fd(fd), buf.cast::(), count as u32) + .offset(offset as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn readv( + &self, + user_data: usize, + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_READV, + Readv, + Readv::new(Fd(fd), iov, iovcnt as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn preadv( + &self, + user_data: usize, + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + offset: off_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_READV, + Readv, + Readv::new(Fd(fd), iov, iovcnt as u32) + .offset(offset as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn recvmsg( + &self, + user_data: usize, + fd: c_int, + msg: *mut msghdr, + flags: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_RECVMSG, + RecvMsg, + RecvMsg::new(Fd(fd), msg) + .flags(flags as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn send( + &self, + user_data: usize, + fd: c_int, + buf: *const c_void, + len: size_t, + flags: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_SEND, + Send, + Send::new(Fd(fd), buf.cast::(), len as u32) + .flags(flags) + .build() + .user_data(user_data as u64) + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn sendto( + &self, + user_data: usize, + fd: c_int, + buf: *const c_void, + len: size_t, + flags: c_int, + addr: *const sockaddr, + addrlen: socklen_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_SEND_ZC, + SendZc, + SendZc::new(Fd(fd), buf.cast::(), len as u32) + .flags(flags) + .dest_addr(addr) + .dest_addr_len(addrlen) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn write( + &self, + user_data: usize, + fd: c_int, + buf: *const c_void, + count: size_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_WRITE, + Write, + Write::new(Fd(fd), buf.cast::(), count as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn pwrite( + &self, + user_data: usize, + fd: c_int, + buf: *const c_void, + count: size_t, + offset: off_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_WRITE, + Write, + Write::new(Fd(fd), buf.cast::(), count as u32) + .offset(offset as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn writev( + &self, + user_data: usize, + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_WRITEV, + Writev, + Writev::new(Fd(fd), iov, iovcnt as u32) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn pwritev( + &self, + user_data: usize, + fd: c_int, + iov: *const iovec, + iovcnt: c_int, + offset: off_t, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_WRITEV, + Writev, + Writev::new(Fd(fd), iov, iovcnt as u32) + .offset(offset as u64) + .build() + .user_data(user_data as u64) + ) + } + + pub(crate) fn sendmsg( + &self, + user_data: usize, + fd: c_int, + msg: *const msghdr, + flags: c_int, + ) -> std::io::Result<()> { + support!( + self, + SUPPORT_SENDMSG, + SendMsg, + SendMsg::new(Fd(fd), msg) + .flags(flags as u32) + .build() + .user_data(user_data as u64) + ) + } +} diff --git a/core/src/net/operator/tests.rs b/core/src/net/operator/tests.rs new file mode 100644 index 00000000..0e306a5b --- /dev/null +++ b/core/src/net/operator/tests.rs @@ -0,0 +1,413 @@ +use crate::net::operator::Operator; +use io_uring::{opcode, squeue, types, IoUring, SubmissionQueue}; +use slab::Slab; +use std::collections::VecDeque; +use std::io::{BufRead, BufReader, Write}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, TcpStream}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use std::{io, ptr}; + +#[derive(Clone, Debug)] +enum Token { + Accept, + Read { + fd: RawFd, + buf_index: usize, + }, + Write { + fd: RawFd, + buf_index: usize, + offset: usize, + len: usize, + }, +} + +struct AcceptCount { + entry: squeue::Entry, + count: usize, +} + +impl AcceptCount { + fn new(fd: RawFd, token: usize, count: usize) -> AcceptCount { + AcceptCount { + entry: opcode::Accept::new(types::Fd(fd), ptr::null_mut(), ptr::null_mut()) + .flags(libc::SOCK_CLOEXEC) + .build() + .user_data(token as _), + count, + } + } + + fn push_to(&mut self, sq: &mut SubmissionQueue<'_>) { + while self.count > 0 { + unsafe { + match sq.push(&self.entry) { + Ok(_) => self.count -= 1, + Err(_) => break, + } + } + } + + sq.sync(); + } +} + +fn crate_server(port: u16, server_started: Arc) -> anyhow::Result<()> { + let mut ring: IoUring = IoUring::builder() + .setup_sqpoll(1000) + .setup_sqpoll_cpu(0) + .build(1024)?; + let listener = TcpListener::bind(("127.0.0.1", port))?; + + let mut backlog = VecDeque::new(); + let mut bufpool = Vec::with_capacity(64); + let mut buf_alloc = Slab::with_capacity(64); + let mut token_alloc = Slab::with_capacity(64); + + println!("listen {}", listener.local_addr()?); + server_started.store(true, Ordering::Release); + + let (submitter, mut sq, mut cq) = ring.split(); + + let mut accept = AcceptCount::new(listener.as_raw_fd(), token_alloc.insert(Token::Accept), 1); + + accept.push_to(&mut sq); + + loop { + match submitter.submit_and_wait(1) { + Ok(_) => (), + Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => (), + Err(err) => return Err(err.into()), + } + cq.sync(); + + // clean backlog + loop { + if sq.is_full() { + match submitter.submit() { + Ok(_) => (), + Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => break, + Err(err) => return Err(err.into()), + } + } + sq.sync(); + + match backlog.pop_front() { + Some(sqe) => unsafe { + let _ = sq.push(&sqe); + }, + None => break, + } + } + + accept.push_to(&mut sq); + + for cqe in &mut cq { + let ret = cqe.result(); + let token_index = cqe.user_data() as usize; + + if ret < 0 { + eprintln!( + "token {:?} error: {:?}", + token_alloc.get(token_index), + io::Error::from_raw_os_error(-ret) + ); + continue; + } + + let token = &mut token_alloc[token_index]; + match token.clone() { + Token::Accept => { + println!("accept"); + + accept.count += 1; + + let fd = ret; + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = vec![0u8; 2048].into_boxed_slice(); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + *token = Token::Read { fd, buf_index }; + + let read_e = opcode::Recv::new(types::Fd(fd), buf.as_mut_ptr(), buf.len() as _) + .build() + .user_data(token_index as _); + + unsafe { + if sq.push(&read_e).is_err() { + backlog.push_back(read_e); + } + } + } + Token::Read { fd, buf_index } => { + if ret == 0 { + bufpool.push(buf_index); + _ = token_alloc.remove(token_index); + println!("shutdown connection"); + unsafe { _ = libc::close(fd) }; + + println!("Server closed"); + return Ok(()); + } else { + let len = ret as usize; + let buf = &buf_alloc[buf_index]; + + *token = Token::Write { + fd, + buf_index, + len, + offset: 0, + }; + + let write_e = opcode::Send::new(types::Fd(fd), buf.as_ptr(), len as _) + .build() + .user_data(token_index as _); + + unsafe { + if sq.push(&write_e).is_err() { + backlog.push_back(write_e); + } + } + } + } + Token::Write { + fd, + buf_index, + offset, + len, + } => { + let write_len = ret as usize; + + let entry = if offset + write_len >= len { + bufpool.push(buf_index); + + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = vec![0u8; 2048].into_boxed_slice(); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + *token = Token::Read { fd, buf_index }; + + opcode::Recv::new(types::Fd(fd), buf.as_mut_ptr(), buf.len() as _) + .build() + .user_data(token_index as _) + } else { + let offset = offset + write_len; + let len = len - offset; + + let buf = &buf_alloc[buf_index][offset..]; + + *token = Token::Write { + fd, + buf_index, + offset, + len, + }; + + opcode::Write::new(types::Fd(fd), buf.as_ptr(), len as _) + .build() + .user_data(token_index as _) + }; + + unsafe { + if sq.push(&entry).is_err() { + backlog.push_back(entry); + } + } + } + } + } + } +} + +fn crate_client(port: u16, server_started: Arc) { + //等服务端起来 + while !server_started.load(Ordering::Acquire) {} + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); + let mut stream = TcpStream::connect_timeout(&socket, Duration::from_secs(3)) + .unwrap_or_else(|_| panic!("connect to 127.0.0.1:{port} failed !")); + let mut data: [u8; 512] = [b'1'; 512]; + data[511] = b'\n'; + let mut buffer: Vec = Vec::with_capacity(512); + for _ in 0..3 { + //写入stream流,如果写入失败,提示"写入失败" + assert_eq!(512, stream.write(&data).expect("Failed to write!")); + print!("Client Send: {}", String::from_utf8_lossy(&data[..])); + + let mut reader = BufReader::new(&stream); + //一直读到换行为止(b'\n'中的b表示字节),读到buffer里面 + assert_eq!( + 512, + reader + .read_until(b'\n', &mut buffer) + .expect("Failed to read into buffer") + ); + print!("Client Received: {}", String::from_utf8_lossy(&buffer[..])); + assert_eq!(&data, &buffer as &[u8]); + buffer.clear(); + } + //发送终止符 + assert_eq!(1, stream.write(&[b'e']).expect("Failed to write!")); + println!("client closed"); +} + +#[test] +fn original() -> anyhow::Result<()> { + let port = 7060; + let server_started = Arc::new(AtomicBool::new(false)); + let clone = server_started.clone(); + let handle = std::thread::spawn(move || crate_server(port, clone)); + std::thread::spawn(move || crate_client(port, server_started)) + .join() + .expect("client has error"); + handle.join().expect("server has error") +} + +fn crate_server2(port: u16, server_started: Arc) -> anyhow::Result<()> { + let operator = Operator::new(0)?; + let listener = TcpListener::bind(("127.0.0.1", port))?; + + let mut bufpool = Vec::with_capacity(64); + let mut buf_alloc = Slab::with_capacity(64); + let mut token_alloc = Slab::with_capacity(64); + + println!("listen {}", listener.local_addr()?); + server_started.store(true, Ordering::Release); + + operator.accept4( + token_alloc.insert(Token::Accept), + listener.as_raw_fd(), + ptr::null_mut(), + ptr::null_mut(), + libc::SOCK_CLOEXEC, + )?; + + loop { + let (_, mut cq, _) = operator.select(None, 1)?; + + for cqe in &mut cq { + let ret = cqe.result(); + let token_index = cqe.user_data() as usize; + + if ret < 0 { + eprintln!( + "token {:?} error: {:?}", + token_alloc.get(token_index), + io::Error::from_raw_os_error(-ret) + ); + continue; + } + + let token = &mut token_alloc[token_index]; + match token.clone() { + Token::Accept => { + println!("accept"); + + let fd = ret; + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = vec![0u8; 2048].into_boxed_slice(); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + *token = Token::Read { fd, buf_index }; + + operator.recv(token_index, fd, buf.as_mut_ptr() as _, buf.len(), 0)?; + } + Token::Read { fd, buf_index } => { + if ret == 0 { + bufpool.push(buf_index); + _ = token_alloc.remove(token_index); + println!("shutdown connection"); + unsafe { _ = libc::close(fd) }; + + println!("Server closed"); + return Ok(()); + } else { + let len = ret as usize; + let buf = &buf_alloc[buf_index]; + + *token = Token::Write { + fd, + buf_index, + len, + offset: 0, + }; + + operator.send(token_index, fd, buf.as_ptr() as _, len, 0)?; + } + } + Token::Write { + fd, + buf_index, + offset, + len, + } => { + let write_len = ret as usize; + + if offset + write_len >= len { + bufpool.push(buf_index); + + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = vec![0u8; 2048].into_boxed_slice(); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + *token = Token::Read { fd, buf_index }; + + operator.recv(token_index, fd, buf.as_mut_ptr() as _, buf.len(), 0)?; + } else { + let offset = offset + write_len; + let len = len - offset; + + let buf = &buf_alloc[buf_index][offset..]; + + *token = Token::Write { + fd, + buf_index, + offset, + len, + }; + + operator.write(token_index, fd, buf.as_ptr() as _, len)?; + }; + } + } + } + } +} + +#[test] +fn framework() -> anyhow::Result<()> { + let port = 7061; + let server_started = Arc::new(AtomicBool::new(false)); + let clone = server_started.clone(); + let handle = std::thread::spawn(move || crate_server2(port, clone)); + std::thread::spawn(move || crate_client(port, server_started)) + .join() + .expect("client has error"); + handle.join().expect("server has error") +} diff --git a/open-coroutine-core/src/net/selector/mio_adapter.rs b/core/src/net/selector/mio_adapter.rs similarity index 73% rename from open-coroutine-core/src/net/selector/mio_adapter.rs rename to core/src/net/selector/mio_adapter.rs index 6f9d9ffd..f8126cac 100644 --- a/open-coroutine-core/src/net/selector/mio_adapter.rs +++ b/core/src/net/selector/mio_adapter.rs @@ -1,10 +1,12 @@ +use crate::common::CondvarBlocker; +use crossbeam_utils::atomic::AtomicCell; +use derivative::Derivative; use mio::event::Event; use mio::unix::SourceFd; use mio::{Events, Interest, Poll, Token}; -use std::cell::UnsafeCell; use std::ffi::c_int; use std::ops::Deref; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::AtomicBool; use std::time::Duration; impl super::Interest for Interest { @@ -44,17 +46,22 @@ impl super::EventIterator for Events { } } -#[derive(Debug)] +#[repr(C)] +#[derive(Derivative)] +#[derivative(Debug)] pub(crate) struct Poller { waiting: AtomicBool, - inner: UnsafeCell, + blocker: CondvarBlocker, + #[derivative(Debug = "ignore")] + inner: AtomicCell, } impl Poller { pub(crate) fn new() -> std::io::Result { Ok(Self { waiting: AtomicBool::new(false), - inner: UnsafeCell::new(Poll::new()?), + blocker: CondvarBlocker::default(), + inner: AtomicCell::new(Poll::new()?), }) } } @@ -63,23 +70,22 @@ impl Deref for Poller { type Target = Poll; fn deref(&self) -> &Self::Target { - unsafe { &*self.inner.get() } + unsafe { &*self.inner.as_ptr() } } } impl super::Selector for Poller { + fn waiting(&self) -> &AtomicBool { + &self.waiting + } + + fn blocker(&self) -> &CondvarBlocker { + &self.blocker + } + fn do_select(&self, events: &mut Events, timeout: Option) -> std::io::Result<()> { - if self - .waiting - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_err() - { - return Ok(()); - } - let inner = unsafe { &mut *self.inner.get() }; - let result = inner.poll(events, timeout); - self.waiting.store(false, Ordering::Release); - result + let inner = unsafe { &mut *self.inner.as_ptr() }; + inner.poll(events, timeout) } fn do_register(&self, fd: c_int, token: usize, interests: Interest) -> std::io::Result<()> { diff --git a/open-coroutine-core/src/net/selector/mod.rs b/core/src/net/selector/mod.rs similarity index 91% rename from open-coroutine-core/src/net/selector/mod.rs rename to core/src/net/selector/mod.rs index 258ae424..23216920 100644 --- a/open-coroutine-core/src/net/selector/mod.rs +++ b/core/src/net/selector/mod.rs @@ -1,6 +1,9 @@ +use crate::common::constants::SLICE; +use crate::common::CondvarBlocker; use dashmap::{DashMap, DashSet}; use once_cell::sync::Lazy; use std::ffi::c_int; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; /// Interest abstraction. @@ -50,7 +53,16 @@ pub(crate) trait Selector> { /// # Errors /// if poll failed. fn select(&self, events: &mut S, timeout: Option) -> std::io::Result<()> { + if self + .waiting() + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + self.blocker().block(timeout.unwrap_or(SLICE)); + return Ok(()); + } let result = self.do_select(events, timeout); + self.waiting().store(false, Ordering::Release); for event in events.iterator() { let token = event.get_token(); let fd = TOKEN_FD.remove(&token).map_or(0, |r| r.1); @@ -184,6 +196,12 @@ pub(crate) trait Selector> { }) } + /// For inner impls. + fn waiting(&self) -> &AtomicBool; + + /// For inner impls. + fn blocker(&self) -> &CondvarBlocker; + /// For inner impls. fn do_select(&self, events: &mut S, timeout: Option) -> std::io::Result<()>; @@ -204,7 +222,7 @@ pub(super) use {mio::Events, mio_adapter::Poller}; mod mio_adapter; #[cfg(windows)] -pub use {polling::Poller, polling_adapter::Events}; +pub(super) use {polling_adapter::Events, polling_adapter::Poller}; #[cfg(windows)] mod polling_adapter; diff --git a/open-coroutine-core/src/net/selector/polling_adapter.rs b/core/src/net/selector/polling_adapter.rs similarity index 68% rename from open-coroutine-core/src/net/selector/polling_adapter.rs rename to core/src/net/selector/polling_adapter.rs index e6475368..9c3ff744 100644 --- a/open-coroutine-core/src/net/selector/polling_adapter.rs +++ b/core/src/net/selector/polling_adapter.rs @@ -1,8 +1,11 @@ -use polling::{Event, PollMode, Poller}; +use crate::common::CondvarBlocker; +use polling::{Event, PollMode}; use std::ffi::c_int; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::AtomicBool; use std::time::Duration; -pub type Events = Vec; +pub(crate) type Events = Vec; impl super::Interest for Event { fn read(token: usize) -> Self { @@ -41,7 +44,47 @@ impl super::EventIterator for Events { } } +#[repr(C)] +#[derive(Debug)] +pub(crate) struct Poller { + waiting: AtomicBool, + blocker: CondvarBlocker, + inner: polling::Poller, +} + +impl Poller { + pub(crate) fn new() -> std::io::Result { + Ok(Self { + waiting: AtomicBool::new(false), + blocker: CondvarBlocker::default(), + inner: polling::Poller::new()?, + }) + } +} + +impl Deref for Poller { + type Target = polling::Poller; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Poller { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + impl super::Selector for Poller { + fn waiting(&self) -> &AtomicBool { + &self.waiting + } + + fn blocker(&self) -> &CondvarBlocker { + &self.blocker + } + fn do_select(&self, events: &mut Events, timeout: Option) -> std::io::Result<()> { self.wait(events, timeout).map(|_| ()) } @@ -49,7 +92,7 @@ impl super::Selector for Poller { fn do_register(&self, fd: c_int, _: usize, interests: Event) -> std::io::Result<()> { cfg_if::cfg_if! { if #[cfg(windows)] { - let source = std::os::windows::io::RawSocket::from(fd as u32); + let source = std::os::windows::io::RawSocket::from(u32::try_from(fd).expect("overflow")); } else { let source = fd; } @@ -68,7 +111,7 @@ impl super::Selector for Poller { fn do_reregister(&self, fd: c_int, _: usize, interests: Event) -> std::io::Result<()> { cfg_if::cfg_if! { if #[cfg(windows)] { - let source = std::os::windows::io::RawSocket::from(fd as u32); + let source = std::os::windows::io::RawSocket::from(u32::try_from(fd).expect("overflow")); } else { let source = fd; } @@ -87,7 +130,7 @@ impl super::Selector for Poller { fn do_deregister(&self, fd: c_int, _: usize) -> std::io::Result<()> { cfg_if::cfg_if! { if #[cfg(windows)] { - let source = std::os::windows::io::RawSocket::from(fd as u32); + let source = std::os::windows::io::RawSocket::from(u32::try_from(fd).expect("overflow")); } else { let source = fd; } diff --git a/core/src/scheduler.rs b/core/src/scheduler.rs new file mode 100644 index 00000000..2b619ad1 --- /dev/null +++ b/core/src/scheduler.rs @@ -0,0 +1,303 @@ +use crate::common::beans::BeanFactory; +use crate::common::constants::{CoroutineState, SyscallState}; +use crate::common::timer::TimerList; +use crate::common::work_steal::{LocalQueue, WorkStealQueue}; +use crate::common::{get_timeout_time, now}; +use crate::coroutine::listener::Listener; +use crate::coroutine::suspender::Suspender; +use crate::coroutine::Coroutine; +use crate::{co, impl_current_for, impl_display_by_debug, impl_for_named}; +use dashmap::DashMap; +use std::collections::VecDeque; +use std::io::{Error, ErrorKind}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +/// A type for Scheduler. +pub type SchedulableCoroutineState = CoroutineState<(), Option>; + +/// A type for Scheduler. +pub type SchedulableCoroutine<'s> = Coroutine<'s, (), (), Option>; + +/// A type for Scheduler. +pub type SchedulableSuspender<'s> = Suspender<'s, (), ()>; + +/// The scheduler impls. +#[repr(C)] +#[derive(Debug)] +pub struct Scheduler<'s> { + name: String, + stack_size: AtomicUsize, + listeners: VecDeque<&'s dyn Listener<(), Option>>, + ready: LocalQueue<'s, SchedulableCoroutine<'s>>, + suspend: TimerList>, + syscall: DashMap<&'s str, SchedulableCoroutine<'s>>, + syscall_suspend: TimerList<&'s str>, + results: DashMap<&'s str, Result, &'s str>>, +} + +impl Default for Scheduler<'_> { + fn default() -> Self { + Self::new( + format!("open-coroutine-scheduler-{:?}", std::thread::current().id()), + crate::common::constants::DEFAULT_STACK_SIZE, + ) + } +} + +impl Drop for Scheduler<'_> { + fn drop(&mut self) { + if std::thread::panicking() { + return; + } + _ = self + .try_timed_schedule(Duration::from_secs(30)) + .unwrap_or_else(|_| panic!("Failed to stop scheduler {} !", self.name())); + assert!( + self.ready.is_empty(), + "There are still coroutines to be carried out in the ready queue:{:#?} !", + self.ready + ); + assert!( + self.suspend.is_empty(), + "There are still coroutines to be carried out in the suspend queue:{:#?} !", + self.suspend + ); + assert!( + self.syscall.is_empty(), + "There are still coroutines to be carried out in the syscall queue:{:#?} !", + self.syscall + ); + } +} + +impl_for_named!(Scheduler<'s>); + +impl_current_for!(SCHEDULER, Scheduler<'s>); + +impl_display_by_debug!(Scheduler<'s>); + +impl<'s> Scheduler<'s> { + /// Creates a new scheduler. + #[must_use] + pub fn new(name: String, stack_size: usize) -> Self { + Scheduler { + name, + stack_size: AtomicUsize::new(stack_size), + listeners: VecDeque::new(), + ready: BeanFactory::get_or_default::>( + crate::common::constants::COROUTINE_GLOBAL_QUEUE_BEAN, + ) + .local_queue(), + suspend: TimerList::default(), + syscall: DashMap::default(), + syscall_suspend: TimerList::default(), + results: DashMap::default(), + } + } + + /// Get the name of this scheduler. + pub fn name(&self) -> &str { + &self.name + } + + /// Get the default stack size for the coroutines in this scheduler. + /// If it has not been set, it will be [`crate::common::constants::DEFAULT_STACK_SIZE`]. + pub fn stack_size(&self) -> usize { + self.stack_size.load(Ordering::Acquire) + } + + /// Submit a closure to create new coroutine, then the coroutine will be push into ready queue. + /// + /// Allow multiple threads to concurrently submit coroutine to the scheduler, + /// but only allow one thread to execute scheduling. + /// + /// # Errors + /// if create coroutine fails. + pub fn submit_co( + &self, + f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + 'static, + stack_size: Option, + ) -> std::io::Result<()> { + let mut co = co!( + format!("{}@{}", self.name(), uuid::Uuid::new_v4()), + f, + stack_size.unwrap_or(self.stack_size()), + )?; + for listener in self.listeners.clone() { + co.add_raw_listener(listener); + } + // let co_name = Box::leak(Box::from(coroutine.get_name())); + self.submit_raw_co(co) + } + + /// Add a listener to this scheduler. + pub fn add_listener(&mut self, listener: impl Listener<(), Option> + 's) { + self.listeners.push_back(Box::leak(Box::new(listener))); + } + + /// Submit a raw coroutine, then the coroutine will be push into ready queue. + /// + /// Allow multiple threads to concurrently submit coroutine to the scheduler, + /// but only allow one thread to execute scheduling. + pub fn submit_raw_co(&self, coroutine: SchedulableCoroutine<'s>) -> std::io::Result<()> { + self.ready.push_back(coroutine); + Ok(()) + } + + /// Resume a coroutine from the system call table to the ready queue, + /// it's generally only required for framework level crates. + /// + /// If we can't find the coroutine, nothing happens. + /// + /// # Errors + /// if change to ready fails. + pub fn try_resume(&self, co_name: &'s str) { + if let Some((_, co)) = self.syscall.remove(&co_name) { + match co.state() { + CoroutineState::SystemCall(val, syscall, SyscallState::Suspend(_)) => { + co.syscall(val, syscall, SyscallState::Callback) + .expect("change syscall state failed"); + } + _ => unreachable!("try_resume unexpect CoroutineState"), + } + self.ready.push_back(co); + } + } + + /// Schedule the coroutines. + /// + /// Allow multiple threads to concurrently submit coroutine to the scheduler, + /// but only allow one thread to execute scheduling. + /// + /// # Errors + /// see `try_timeout_schedule`. + pub fn try_schedule(&mut self) -> std::io::Result<()> { + self.try_timeout_schedule(u64::MAX).map(|_| ()) + } + + /// Try scheduling the coroutines for up to `dur`. + /// + /// Allow multiple threads to concurrently submit coroutine to the scheduler, + /// but only allow one thread to execute scheduling. + /// + /// # Errors + /// see `try_timeout_schedule`. + pub fn try_timed_schedule(&mut self, dur: Duration) -> std::io::Result { + self.try_timeout_schedule(get_timeout_time(dur)) + } + + /// Attempt to schedule the coroutines before the `timeout_time` timestamp. + /// + /// Allow multiple threads to concurrently submit coroutine to the scheduler, + /// but only allow one thread to schedule. + /// + /// Returns the left time in ns. + /// + /// # Errors + /// if change to ready fails. + pub fn try_timeout_schedule(&mut self, timeout_time: u64) -> std::io::Result { + Self::init_current(self); + let left_time = self.do_schedule(timeout_time); + Self::clean_current(); + left_time + } + + fn do_schedule(&mut self, timeout_time: u64) -> std::io::Result { + loop { + let left_time = timeout_time.saturating_sub(now()); + if 0 == left_time { + return Ok(0); + } + self.check_ready()?; + // schedule coroutines + if let Some(mut coroutine) = self.ready.pop_front() { + match coroutine.resume()? { + CoroutineState::SystemCall((), _, state) => { + //挂起协程到系统调用表 + let co_name = Box::leak(Box::from(coroutine.name())); + //如果已包含,说明当前系统调用还有上层父系统调用,因此直接忽略插入结果 + _ = self.syscall.insert(co_name, coroutine); + if let SyscallState::Suspend(timestamp) = state { + self.syscall_suspend.insert(timestamp, co_name); + } + } + CoroutineState::Suspend((), timestamp) => { + if timestamp > now() { + //挂起协程到时间轮 + self.suspend.insert(timestamp, coroutine); + } else { + //放入就绪队列尾部 + self.ready.push_back(coroutine); + } + } + CoroutineState::Complete(result) => { + let co_name = Box::leak(Box::from(coroutine.name())); + assert!( + self.results.insert(co_name, Ok(result)).is_none(), + "not consume result" + ); + } + CoroutineState::Error(message) => { + let co_name = Box::leak(Box::from(coroutine.name())); + assert!( + self.results.insert(co_name, Err(message)).is_none(), + "not consume result" + ); + } + _ => { + return Err(Error::new( + ErrorKind::Other, + "try_timeout_schedule should never execute to here", + )); + } + } + continue; + } + return Ok(left_time); + } + } + + fn check_ready(&mut self) -> std::io::Result<()> { + // Check if the elements in the suspend queue are ready + for _ in 0..self.suspend.entry_len() { + if let Some((exec_time, _)) = self.suspend.front() { + if now() < *exec_time { + break; + } + if let Some((_, mut entry)) = self.suspend.pop_front() { + while let Some(coroutine) = entry.pop_front() { + coroutine.ready()?; + self.ready.push_back(coroutine); + } + } + } + } + // Check if the elements in the syscall suspend queue are ready + for _ in 0..self.syscall_suspend.entry_len() { + if let Some((exec_time, _)) = self.syscall_suspend.front() { + if now() < *exec_time { + break; + } + if let Some((_, mut entry)) = self.syscall_suspend.pop_front() { + while let Some(co_name) = entry.pop_front() { + if let Some((_, co)) = self.syscall.remove(&co_name) { + match co.state() { + CoroutineState::SystemCall( + val, + syscall, + SyscallState::Suspend(_), + ) => { + co.syscall(val, syscall, SyscallState::Timeout)?; + self.ready.push_back(co); + } + _ => unreachable!("check_ready should never execute to here"), + } + } + } + } + } + } + Ok(()) + } +} diff --git a/core/src/syscall/common.rs b/core/src/syscall/common.rs new file mode 100644 index 00000000..24f084e1 --- /dev/null +++ b/core/src/syscall/common.rs @@ -0,0 +1,39 @@ +pub use crate::syscall::{is_blocking, is_non_blocking, set_blocking, set_errno, set_non_blocking}; + +pub extern "C" fn reset_errno() { + set_errno(0); +} + +#[macro_export] +macro_rules! log_syscall { + ( $socket:expr, $done:expr, $once_result:expr ) => { + #[cfg(feature = "logs")] + if let Some(coroutine) = $crate::scheduler::SchedulableCoroutine::current() { + $crate::info!( + "{} {} {} {} {} {}", + coroutine.get_name(), + coroutine.state(), + $socket, + $done, + $once_result, + std::io::Error::last_os_error(), + ); + } + }; +} + +#[macro_export] +macro_rules! impl_non_blocking { + ( $socket:expr, $impls:expr ) => {{ + let socket = $socket; + let blocking = $crate::syscall::common::is_blocking(socket); + if blocking { + $crate::syscall::common::set_non_blocking(socket); + } + let r = $impls; + if blocking { + $crate::syscall::common::set_blocking(socket); + } + return r; + }}; +} diff --git a/open-coroutine-core/src/syscall/mod.rs b/core/src/syscall/mod.rs similarity index 73% rename from open-coroutine-core/src/syscall/mod.rs rename to core/src/syscall/mod.rs index a0676c19..81ab16c8 100644 --- a/open-coroutine-core/src/syscall/mod.rs +++ b/core/src/syscall/mod.rs @@ -1,4 +1,3 @@ -#[cfg(unix)] pub mod common; #[cfg(unix)] @@ -10,6 +9,6 @@ mod unix; #[cfg(windows)] pub use windows::*; -#[allow(non_snake_case, dead_code)] +#[allow(non_snake_case)] #[cfg(windows)] mod windows; diff --git a/open-coroutine-core/src/syscall/unix/accept.rs b/core/src/syscall/unix/accept.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/accept.rs rename to core/src/syscall/unix/accept.rs diff --git a/open-coroutine-core/src/syscall/unix/accept4.rs b/core/src/syscall/unix/accept4.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/accept4.rs rename to core/src/syscall/unix/accept4.rs diff --git a/open-coroutine-core/src/syscall/unix/close.rs b/core/src/syscall/unix/close.rs similarity index 94% rename from open-coroutine-core/src/syscall/unix/close.rs rename to core/src/syscall/unix/close.rs index d6eabcbf..251553b7 100644 --- a/open-coroutine-core/src/syscall/unix/close.rs +++ b/core/src/syscall/unix/close.rs @@ -1,4 +1,4 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use once_cell::sync::Lazy; use std::ffi::c_int; @@ -25,6 +25,7 @@ impl_facade!(CloseSyscallFacade, CloseSyscall, close(fd: c_int) -> c_int); impl_io_uring!(IoUringCloseSyscall, CloseSyscall, close(fd: c_int) -> c_int); +#[repr(C)] #[derive(Debug, Default)] struct NioCloseSyscall { inner: I, @@ -32,7 +33,7 @@ struct NioCloseSyscall { impl CloseSyscall for NioCloseSyscall { extern "C" fn close(&self, fn_ptr: Option<&extern "C" fn(c_int) -> c_int>, fd: c_int) -> c_int { - EventLoops::del_event(fd); + _ = EventLoops::del_event(fd); self.inner.close(fn_ptr, fd) } } diff --git a/open-coroutine-core/src/syscall/unix/connect.rs b/core/src/syscall/unix/connect.rs similarity index 88% rename from open-coroutine-core/src/syscall/unix/connect.rs rename to core/src/syscall/unix/connect.rs index ebfedf5d..501fdb9e 100644 --- a/open-coroutine-core/src/syscall/unix/connect.rs +++ b/core/src/syscall/unix/connect.rs @@ -1,10 +1,9 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use crate::syscall::common::{is_blocking, reset_errno, set_blocking, set_errno, set_non_blocking}; use libc::{sockaddr, socklen_t}; use once_cell::sync::Lazy; use std::ffi::{c_int, c_void}; use std::io::Error; -use std::time::Duration; #[must_use] pub extern "C" fn connect( @@ -44,6 +43,7 @@ impl_io_uring!(IoUringConnectSyscall, ConnectSyscall, connect(fd: c_int, address: *const sockaddr, len: socklen_t) -> c_int ); +#[repr(C)] #[derive(Debug, Default)] struct NioConnectSyscall { inner: I, @@ -62,16 +62,16 @@ impl ConnectSyscall for NioConnectSyscall { set_non_blocking(fd); } let mut r = self.inner.connect(fn_ptr, fd, address, len); - if r == 0 { - reset_errno(); - return r; - } loop { + if r == 0 { + reset_errno(); + break; + } let errno = Error::last_os_error().raw_os_error(); - if errno == Some(libc::EINPROGRESS) || errno == Some(libc::ENOTCONN) { + if errno == Some(libc::EINPROGRESS) || errno == Some(libc::EALREADY) { //阻塞,直到写事件发生 - if EventLoops::wait_write_event(fd, Some(Duration::from_millis(10))).is_err() { - r = -1; + if EventLoops::wait_write_event(fd, Some(crate::common::constants::SLICE)).is_err() + { break; } let mut err: c_int = 0; @@ -99,13 +99,7 @@ impl ConnectSyscall for NioConnectSyscall { let mut address_len = std::mem::zeroed(); r = libc::getpeername(fd, &mut address, &mut address_len); } - if r == 0 { - reset_errno(); - r = 0; - break; - } } else if errno != Some(libc::EINTR) { - r = -1; break; } } diff --git a/open-coroutine-core/src/syscall/unix/listen.rs b/core/src/syscall/unix/listen.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/listen.rs rename to core/src/syscall/unix/listen.rs diff --git a/core/src/syscall/unix/mod.rs b/core/src/syscall/unix/mod.rs new file mode 100644 index 00000000..b8b21827 --- /dev/null +++ b/core/src/syscall/unix/mod.rs @@ -0,0 +1,662 @@ +use std::ffi::c_int; + +pub use accept::accept; +#[cfg(target_os = "linux")] +pub use accept4::accept4; +pub use close::close; +pub use connect::connect; +pub use listen::listen; +pub use nanosleep::nanosleep; +pub use poll::poll; +pub use pread::pread; +pub use preadv::preadv; +pub use pthread_cond_timedwait::pthread_cond_timedwait; +pub use pthread_mutex_lock::pthread_mutex_lock; +pub use pthread_mutex_trylock::pthread_mutex_trylock; +pub use pthread_mutex_unlock::pthread_mutex_unlock; +pub use pwrite::pwrite; +pub use pwritev::pwritev; +pub use read::read; +pub use readv::readv; +pub use recv::recv; +pub use recvfrom::recvfrom; +pub use recvmsg::recvmsg; +pub use select::select; +pub use send::send; +pub use sendmsg::sendmsg; +pub use sendto::sendto; +pub use shutdown::shutdown; +pub use sleep::sleep; +pub use socket::socket; +pub use usleep::usleep; +pub use write::write; +pub use writev::writev; + +macro_rules! impl_facade { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, + $($arg: $arg_type),* + ) -> $result { + let syscall = $crate::common::constants::Syscall::$syscall; + $crate::info!("enter syscall {}", syscall); + if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() { + let new_state = $crate::common::constants::SyscallState::Executing; + if co.syscall((), syscall, new_state).is_err() { + $crate::error!("{} change to syscall {} {} failed !", + co.name(), syscall, new_state); + } + } + let r = self.inner.$syscall(fn_ptr, $($arg, )*); + if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() { + if co.running().is_err() { + $crate::error!("{} change to running state failed !", co.name()); + } + } + $crate::info!("exit syscall {}", syscall); + r + } + } + } +} + +macro_rules! impl_io_uring { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + #[cfg(all(target_os = "linux", feature = "io_uring"))] + struct $struct_name { + inner: I, + } + + #[cfg(all(target_os = "linux", feature = "io_uring"))] + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, + $($arg: $arg_type),* + ) -> $result { + if let Ok(arc) = $crate::net::EventLoops::$syscall($($arg, )*) { + use $crate::common::constants::{CoroutineState, SyscallState}; + use $crate::scheduler::{SchedulableCoroutine, SchedulableSuspender}; + + if let Some(co) = SchedulableCoroutine::current() { + if let CoroutineState::SystemCall((), syscall, SyscallState::Executing) = co.state() + { + let new_state = SyscallState::Suspend(u64::MAX); + if co.syscall((), syscall, new_state).is_err() { + $crate::error!( + "{} change to syscall {} {} failed !", + co.name(), + syscall, + new_state + ); + } + } + } + if let Some(suspender) = SchedulableSuspender::current() { + suspender.suspend(); + //回来的时候,系统调用已经执行完了 + } + if let Some(co) = SchedulableCoroutine::current() { + if let CoroutineState::SystemCall((), syscall, SyscallState::Callback) = co.state() + { + let new_state = SyscallState::Executing; + if co.syscall((), syscall, new_state).is_err() { + $crate::error!( + "{} change to syscall {} {} failed !", + co.name(), + syscall, + new_state + ); + } + } + } + let (lock, cvar) = &*arc; + let mut syscall_result: $result = cvar + .wait_while(lock.lock().expect("lock failed"), + |&mut result| result.is_none() + ) + .expect("lock failed") + .expect("no syscall result") + .try_into() + .expect("io_uring syscall result overflow"); + if syscall_result < 0 { + let errno: std::ffi::c_int = (-syscall_result).try_into() + .expect("io_uring errno overflow"); + $crate::syscall::common::set_errno(errno); + syscall_result = -1; + } + return syscall_result; + } + self.inner.$syscall(fn_ptr, $($arg, )*) + } + } + } +} + +macro_rules! impl_nio_read { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($fd_type, $($arg_type),*) -> $result>, + $fd: $fd_type, + $($arg: $arg_type),* + ) -> $result { + let blocking = $crate::syscall::common::is_blocking($fd); + if blocking { + $crate::syscall::common::set_non_blocking($fd); + } + let mut r; + loop { + r = self.inner.$syscall(fn_ptr, $fd, $($arg, )*); + if r != -1 { + $crate::syscall::common::reset_errno(); + break; + } + let error_kind = std::io::Error::last_os_error().kind(); + if error_kind == std::io::ErrorKind::WouldBlock { + //wait read event + if $crate::net::EventLoops::wait_read_event( + $fd, + Some($crate::common::constants::SLICE), + ).is_err() { + break; + } + } else if error_kind != std::io::ErrorKind::Interrupted { + break; + } + } + if blocking { + $crate::syscall::common::set_blocking($fd); + } + r + } + } + } +} + +macro_rules! impl_nio_read_buf { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, + $buf: ident : $buf_type: ty, $len: ident : $len_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, + $fd: $fd_type, + $buf: $buf_type, + $len: $len_type, + $($arg: $arg_type),* + ) -> $result { + let blocking = $crate::syscall::common::is_blocking($fd); + if blocking { + $crate::syscall::common::set_non_blocking($fd); + } + let mut received = 0; + let mut r = 0; + while received < $len { + r = self.inner.$syscall( + fn_ptr, + $fd, + ($buf as usize + received) as *mut std::ffi::c_void, + $len - received, + $($arg, )* + ); + if r != -1 { + $crate::syscall::common::reset_errno(); + received += r as size_t; + if received >= $len || r == 0 { + r = received as ssize_t; + break; + } + } + let error_kind = std::io::Error::last_os_error().kind(); + if error_kind == std::io::ErrorKind::WouldBlock { + //wait read event + if $crate::net::EventLoops::wait_read_event( + $fd, + Some($crate::common::constants::SLICE), + ) + .is_err() + { + break; + } + } else if error_kind != std::io::ErrorKind::Interrupted { + break; + } + } + if blocking { + $crate::syscall::common::set_blocking($fd); + } + r + } + } + } +} + +macro_rules! impl_nio_read_iovec { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, + $iov: ident : $iov_type: ty, $iovcnt: ident : $iovcnt_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, + $fd: $fd_type, + $iov: $iov_type, + $iovcnt: $iovcnt_type, + $($arg: $arg_type),* + ) -> $result { + let blocking = $crate::syscall::common::is_blocking($fd); + if blocking { + $crate::syscall::common::set_non_blocking($fd); + } + let vec = unsafe { Vec::from_raw_parts($iov.cast_mut(), $iovcnt as usize, $iovcnt as usize) }; + let mut length = 0; + let mut received = 0usize; + let mut r = 0; + let mut index = 0; + for iovec in &vec { + let mut offset = received.saturating_sub(length); + length += iovec.iov_len; + if received > length { + index += 1; + continue; + } + let mut arg = Vec::new(); + for i in vec.iter().skip(index) { + arg.push(*i); + } + while received < length { + if 0 != offset { + arg[0] = libc::iovec { + iov_base: (arg[0].iov_base as usize + offset) as *mut std::ffi::c_void, + iov_len: arg[0].iov_len - offset, + }; + } + r = self.inner.$syscall( + fn_ptr, + $fd, + arg.as_ptr(), + std::ffi::c_int::try_from(arg.len()).unwrap_or_else(|_| { + panic!("{} iovcnt overflow", $crate::common::constants::Syscall::$syscall) + }), + $($arg, )* + ); + if r == 0 { + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + return r; + } else if r != -1 { + $crate::syscall::common::reset_errno(); + received += r as usize; + if received >= length { + r = received as ssize_t; + break; + } + offset = received.saturating_sub(length); + } + let error_kind = std::io::Error::last_os_error().kind(); + if error_kind == std::io::ErrorKind::WouldBlock { + //wait read event + if $crate::net::EventLoops::wait_read_event( + $fd, + Some($crate::common::constants::SLICE) + ).is_err() { + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + return r; + } + } else if error_kind != std::io::ErrorKind::Interrupted { + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + return r; + } + } + if received >= length { + index += 1; + } + } + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + r + } + } + } +} + +macro_rules! impl_nio_write_buf { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, + $buf: ident : $buf_type: ty, $len: ident : $len_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, + $fd: $fd_type, + $buf: $buf_type, + $len: $len_type, + $($arg: $arg_type),* + ) -> $result { + let blocking = $crate::syscall::common::is_blocking($fd); + if blocking { + $crate::syscall::common::set_non_blocking($fd); + } + let mut sent = 0; + let mut r = 0; + while sent < $len { + r = self.inner.$syscall( + fn_ptr, + $fd, + ($buf as usize + sent) as *const std::ffi::c_void, + $len - sent, + $($arg, )* + ); + if r != -1 { + $crate::syscall::common::reset_errno(); + sent += r as size_t; + if sent >= $len { + r = sent as ssize_t; + break; + } + } + let error_kind = std::io::Error::last_os_error().kind(); + if error_kind == std::io::ErrorKind::WouldBlock { + //wait write event + if $crate::net::EventLoops::wait_write_event( + $fd, + Some($crate::common::constants::SLICE), + ) + .is_err() + { + break; + } + } else if error_kind != std::io::ErrorKind::Interrupted { + break; + } + } + if blocking { + $crate::syscall::common::set_blocking($fd); + } + r + } + } + } +} + +macro_rules! impl_nio_write_iovec { + ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, + $iov: ident : $iov_type: ty, $iovcnt: ident : $iovcnt_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Default)] + struct $struct_name { + inner: I, + } + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, + $fd: $fd_type, + $iov: $iov_type, + $iovcnt: $iovcnt_type, + $($arg: $arg_type),* + ) -> $result { + let blocking = $crate::syscall::common::is_blocking($fd); + if blocking { + $crate::syscall::common::set_non_blocking($fd); + } + let vec = unsafe { Vec::from_raw_parts($iov.cast_mut(), $iovcnt as usize, $iovcnt as usize) }; + let mut length = 0; + let mut sent = 0usize; + let mut r = 0; + let mut index = 0; + for iovec in &vec { + let mut offset = sent.saturating_sub(length); + length += iovec.iov_len; + if sent > length { + index += 1; + continue; + } + let mut arg = Vec::new(); + for i in vec.iter().skip(index) { + arg.push(*i); + } + while sent < length { + if 0 != offset { + arg[0] = libc::iovec { + iov_base: (arg[0].iov_base as usize + offset) as *mut std::ffi::c_void, + iov_len: arg[0].iov_len - offset, + }; + } + r = self.inner.$syscall( + fn_ptr, + $fd, + arg.as_ptr(), + std::ffi::c_int::try_from(arg.len()).unwrap_or_else(|_| { + panic!("{} iovcnt overflow", $crate::common::constants::Syscall::$syscall) + }), + $($arg, )* + ); + if r != -1 { + $crate::syscall::common::reset_errno(); + sent += r as usize; + if sent >= length { + r = sent as ssize_t; + break; + } + offset = sent.saturating_sub(length); + } + let error_kind = std::io::Error::last_os_error().kind(); + if error_kind == std::io::ErrorKind::WouldBlock { + //wait write event + if $crate::net::EventLoops::wait_write_event( + $fd, + Some($crate::common::constants::SLICE) + ).is_err() { + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + return r; + } + } else if error_kind != std::io::ErrorKind::Interrupted { + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + return r; + } + } + if sent >= length { + index += 1; + } + } + std::mem::forget(vec); + if blocking { + $crate::syscall::common::set_blocking($fd); + } + r + } + } + } +} + +macro_rules! impl_raw { + ( $struct_name: ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] + #[derive(Debug, Copy, Clone, Default)] + struct $struct_name {} + + impl $trait_name for $struct_name { + extern "C" fn $syscall( + &self, + fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, + $($arg: $arg_type),* + ) -> $result { + if let Some(f) = fn_ptr { + (f)($($arg),*) + } else { + unsafe { libc::$syscall($($arg),*) } + } + } + } + } +} + +mod accept; +#[cfg(target_os = "linux")] +mod accept4; +mod close; +mod connect; +mod listen; +mod nanosleep; +mod poll; +mod pread; +mod preadv; +mod pthread_cond_timedwait; +mod pthread_mutex_lock; +mod pthread_mutex_trylock; +mod pthread_mutex_unlock; +mod pwrite; +mod pwritev; +mod read; +mod readv; +mod recv; +mod recvfrom; +mod recvmsg; +mod select; +mod send; +mod sendmsg; +mod sendto; +mod shutdown; +mod sleep; +mod socket; +mod usleep; +mod write; +mod writev; + +extern "C" { + #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks")))] + #[cfg_attr( + any( + target_os = "linux", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "l4re" + ), + link_name = "__errno_location" + )] + #[cfg_attr( + any( + target_os = "netbsd", + target_os = "openbsd", + target_os = "android", + target_os = "redox", + target_env = "newlib" + ), + link_name = "__errno" + )] + #[cfg_attr( + any(target_os = "solaris", target_os = "illumos"), + link_name = "___errno" + )] + #[cfg_attr( + any( + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "watchos" + ), + link_name = "__error" + )] + #[cfg_attr(target_os = "haiku", link_name = "_errnop")] + fn errno_location() -> *mut c_int; +} + +pub extern "C" fn set_errno(errno: c_int) { + unsafe { errno_location().write(errno) } +} + +/// # Panics +/// if set fails. +pub extern "C" fn set_non_blocking(fd: c_int) { + assert!(set_non_blocking_flag(fd, true), "set_non_blocking failed !"); +} + +/// # Panics +/// if set fails. +pub extern "C" fn set_blocking(fd: c_int) { + assert!(set_non_blocking_flag(fd, false), "set_blocking failed !"); +} + +extern "C" fn set_non_blocking_flag(fd: c_int, on: bool) -> bool { + let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + if flags < 0 { + return false; + } + unsafe { + libc::fcntl( + fd, + libc::F_SETFL, + if on { + flags | libc::O_NONBLOCK + } else { + flags & !libc::O_NONBLOCK + }, + ) == 0 + } +} + +#[must_use] +pub extern "C" fn is_blocking(fd: c_int) -> bool { + !is_non_blocking(fd) +} + +#[must_use] +pub extern "C" fn is_non_blocking(fd: c_int) -> bool { + let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) }; + if flags < 0 { + return false; + } + (flags & libc::O_NONBLOCK) != 0 +} diff --git a/open-coroutine-core/src/syscall/unix/nanosleep.rs b/core/src/syscall/unix/nanosleep.rs similarity index 79% rename from open-coroutine-core/src/syscall/unix/nanosleep.rs rename to core/src/syscall/unix/nanosleep.rs index de86a22d..35d6d91b 100644 --- a/open-coroutine-core/src/syscall/unix/nanosleep.rs +++ b/core/src/syscall/unix/nanosleep.rs @@ -1,11 +1,7 @@ -use crate::common::{Current, Named}; -use crate::constants::{Syscall, SyscallState}; -use crate::net::event_loop::EventLoops; -use crate::scheduler::SchedulableCoroutine; +use crate::net::EventLoops; use crate::syscall::common::{reset_errno, set_errno}; use libc::timespec; use once_cell::sync::Lazy; -use open_coroutine_timer::get_timeout_time; use std::ffi::c_int; use std::time::Duration; @@ -32,6 +28,7 @@ impl_facade!(NanosleepSyscallFacade, NanosleepSyscall, nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int ); +#[repr(C)] #[derive(Debug, Copy, Clone, Default)] struct NioNanosleepSyscall {} @@ -48,20 +45,22 @@ impl NanosleepSyscall for NioNanosleepSyscall { return -1; } let time = Duration::new(rqtp.tv_sec as u64, rqtp.tv_nsec as u32); - if let Some(co) = SchedulableCoroutine::current() { - let syscall = Syscall::nanosleep; - let new_state = SyscallState::Suspend(get_timeout_time(time)); + if let Some(co) = crate::scheduler::SchedulableCoroutine::current() { + let syscall = crate::common::constants::Syscall::nanosleep; + let new_state = crate::common::constants::SyscallState::Suspend( + crate::common::get_timeout_time(time), + ); if co.syscall((), syscall, new_state).is_err() { crate::error!( "{} change to syscall {} {} failed !", - co.get_name(), + co.name(), syscall, new_state ); } } //等待事件到来 - _ = EventLoops::wait_just(Some(time)); + _ = EventLoops::wait_event(Some(time)); reset_errno(); if !rmtp.is_null() { unsafe { diff --git a/open-coroutine-core/src/syscall/unix/poll.rs b/core/src/syscall/unix/poll.rs similarity index 93% rename from open-coroutine-core/src/syscall/unix/poll.rs rename to core/src/syscall/unix/poll.rs index 4952407b..4e4d0fce 100644 --- a/open-coroutine-core/src/syscall/unix/poll.rs +++ b/core/src/syscall/unix/poll.rs @@ -1,4 +1,4 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use libc::{nfds_t, pollfd}; use once_cell::sync::Lazy; use std::ffi::c_int; @@ -30,6 +30,7 @@ impl_facade!(PollSyscallFacade, PollSyscall, poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int ); +#[repr(C)] #[derive(Debug, Default)] struct NioPollSyscall { inner: I, @@ -52,7 +53,7 @@ impl PollSyscall for NioPollSyscall { if r != 0 || t == 0 { break; } - _ = EventLoops::wait_just(Some(Duration::from_millis(t.min(x) as u64))); + _ = EventLoops::wait_event(Some(Duration::from_millis(t.min(x) as u64))); if t != c_int::MAX { t = if t > x { t - x } else { 0 }; } diff --git a/open-coroutine-core/src/syscall/unix/pread.rs b/core/src/syscall/unix/pread.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/pread.rs rename to core/src/syscall/unix/pread.rs diff --git a/open-coroutine-core/src/syscall/unix/preadv.rs b/core/src/syscall/unix/preadv.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/preadv.rs rename to core/src/syscall/unix/preadv.rs diff --git a/core/src/syscall/unix/pthread_cond_timedwait.rs b/core/src/syscall/unix/pthread_cond_timedwait.rs new file mode 100644 index 00000000..975c843f --- /dev/null +++ b/core/src/syscall/unix/pthread_cond_timedwait.rs @@ -0,0 +1,113 @@ +use crate::common::now; +use crate::net::EventLoops; +use libc::{pthread_cond_t, pthread_mutex_t, timespec}; +use once_cell::sync::Lazy; +use std::ffi::c_int; +use std::time::Duration; + +#[must_use] +pub extern "C" fn pthread_cond_timedwait( + fn_ptr: Option< + &extern "C" fn(*mut pthread_cond_t, *mut pthread_mutex_t, *const timespec) -> c_int, + >, + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + abstime: *const timespec, +) -> c_int { + static CHAIN: Lazy< + PthreadCondTimedwaitSyscallFacade< + NioPthreadCondTimedwaitSyscall, + >, + > = Lazy::new(Default::default); + CHAIN.pthread_cond_timedwait(fn_ptr, cond, lock, abstime) +} + +trait PthreadCondTimedwaitSyscall { + extern "C" fn pthread_cond_timedwait( + &self, + fn_ptr: Option< + &extern "C" fn(*mut pthread_cond_t, *mut pthread_mutex_t, *const timespec) -> c_int, + >, + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + abstime: *const timespec, + ) -> c_int; +} + +impl_facade!(PthreadCondTimedwaitSyscallFacade, PthreadCondTimedwaitSyscall, + pthread_cond_timedwait( + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + abstime: *const timespec + ) -> c_int +); + +#[repr(C)] +#[derive(Debug, Default)] +struct NioPthreadCondTimedwaitSyscall { + inner: I, +} + +impl PthreadCondTimedwaitSyscall + for NioPthreadCondTimedwaitSyscall +{ + extern "C" fn pthread_cond_timedwait( + &self, + fn_ptr: Option< + &extern "C" fn(*mut pthread_cond_t, *mut pthread_mutex_t, *const timespec) -> c_int, + >, + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + abstime: *const timespec, + ) -> c_int { + let abstimeout = if abstime.is_null() { + u64::MAX + } else { + let abstime = unsafe { *abstime }; + if abstime.tv_sec < 0 || abstime.tv_nsec < 0 || abstime.tv_nsec > 999_999_999 { + return libc::EINVAL; + } + u64::try_from(Duration::new(abstime.tv_sec as u64, abstime.tv_nsec as u32).as_nanos()) + .unwrap_or(u64::MAX) + }; + loop { + let r = self.inner.pthread_cond_timedwait( + fn_ptr, + cond, + lock, + ×pec { + tv_sec: (now() / 1_000_000_000 + 1) as _, + tv_nsec: 0, + }, + ); + if libc::ETIMEDOUT != r { + return r; + } + let left_time = abstimeout.saturating_sub(now()); + if 0 == left_time { + return libc::ETIMEDOUT; + } + let wait_time = if left_time > 10_000_000 { + 10_000_000 + } else { + left_time + }; + if EventLoops::wait_event(Some(Duration::new( + wait_time / 1_000_000_000, + (wait_time % 1_000_000_000) as _, + ))) + .is_err() + { + return r; + } + } + } +} + +impl_raw!(RawPthreadCondTimedwaitSyscall, PthreadCondTimedwaitSyscall, + pthread_cond_timedwait( + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + abstime: *const timespec + ) -> c_int +); diff --git a/core/src/syscall/unix/pthread_mutex_lock.rs b/core/src/syscall/unix/pthread_mutex_lock.rs new file mode 100644 index 00000000..8862d522 --- /dev/null +++ b/core/src/syscall/unix/pthread_mutex_lock.rs @@ -0,0 +1,59 @@ +use crate::net::EventLoops; +use crate::scheduler::SchedulableCoroutine; +use libc::pthread_mutex_t; +use once_cell::sync::Lazy; +use std::ffi::c_int; + +#[must_use] +pub extern "C" fn pthread_mutex_lock( + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, +) -> c_int { + static CHAIN: Lazy< + PthreadMutexLockSyscallFacade>, + > = Lazy::new(Default::default); + CHAIN.pthread_mutex_lock(fn_ptr, lock) +} + +trait PthreadMutexLockSyscall { + extern "C" fn pthread_mutex_lock( + &self, + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, + ) -> c_int; +} + +impl_facade!(PthreadMutexLockSyscallFacade, PthreadMutexLockSyscall, + pthread_mutex_lock(lock: *mut pthread_mutex_t) -> c_int +); + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +struct NioPthreadMutexLockSyscall { + inner: I, +} + +impl PthreadMutexLockSyscall for NioPthreadMutexLockSyscall { + extern "C" fn pthread_mutex_lock( + &self, + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, + ) -> c_int { + if SchedulableCoroutine::current().is_none() { + return self.inner.pthread_mutex_lock(fn_ptr, lock); + } + loop { + let r = unsafe { libc::pthread_mutex_trylock(lock) }; + if 0 == r + || r != libc::EBUSY + || EventLoops::wait_event(Some(crate::common::constants::SLICE)).is_err() + { + return r; + } + } + } +} + +impl_raw!(RawPthreadMutexLockSyscall, PthreadMutexLockSyscall, + pthread_mutex_lock(lock: *mut pthread_mutex_t) -> c_int +); diff --git a/core/src/syscall/unix/pthread_mutex_trylock.rs b/core/src/syscall/unix/pthread_mutex_trylock.rs new file mode 100644 index 00000000..cde1958d --- /dev/null +++ b/core/src/syscall/unix/pthread_mutex_trylock.rs @@ -0,0 +1,29 @@ +use libc::pthread_mutex_t; +use once_cell::sync::Lazy; +use std::ffi::c_int; + +#[must_use] +pub extern "C" fn pthread_mutex_trylock( + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, +) -> c_int { + static CHAIN: Lazy> = + Lazy::new(Default::default); + CHAIN.pthread_mutex_trylock(fn_ptr, lock) +} + +trait PthreadMutexTrylockSyscall { + extern "C" fn pthread_mutex_trylock( + &self, + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, + ) -> c_int; +} + +impl_facade!(PthreadMutexTrylockSyscallFacade, PthreadMutexTrylockSyscall, + pthread_mutex_trylock(lock: *mut pthread_mutex_t) -> c_int +); + +impl_raw!(RawPthreadMutexTrylockSyscall, PthreadMutexTrylockSyscall, + pthread_mutex_trylock(lock: *mut pthread_mutex_t) -> c_int +); diff --git a/core/src/syscall/unix/pthread_mutex_unlock.rs b/core/src/syscall/unix/pthread_mutex_unlock.rs new file mode 100644 index 00000000..6308b2c7 --- /dev/null +++ b/core/src/syscall/unix/pthread_mutex_unlock.rs @@ -0,0 +1,29 @@ +use libc::pthread_mutex_t; +use once_cell::sync::Lazy; +use std::ffi::c_int; + +#[must_use] +pub extern "C" fn pthread_mutex_unlock( + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, +) -> c_int { + static CHAIN: Lazy> = + Lazy::new(Default::default); + CHAIN.pthread_mutex_unlock(fn_ptr, lock) +} + +trait PthreadMutexUnlockSyscall { + extern "C" fn pthread_mutex_unlock( + &self, + fn_ptr: Option<&extern "C" fn(*mut pthread_mutex_t) -> c_int>, + lock: *mut pthread_mutex_t, + ) -> c_int; +} + +impl_facade!(PthreadMutexUnlockSyscallFacade, PthreadMutexUnlockSyscall, + pthread_mutex_unlock(lock: *mut pthread_mutex_t) -> c_int +); + +impl_raw!(RawPthreadMutexUnlockSyscall, PthreadMutexUnlockSyscall, + pthread_mutex_unlock(lock: *mut pthread_mutex_t) -> c_int +); diff --git a/open-coroutine-core/src/syscall/unix/pwrite.rs b/core/src/syscall/unix/pwrite.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/pwrite.rs rename to core/src/syscall/unix/pwrite.rs diff --git a/open-coroutine-core/src/syscall/unix/pwritev.rs b/core/src/syscall/unix/pwritev.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/pwritev.rs rename to core/src/syscall/unix/pwritev.rs diff --git a/open-coroutine-core/src/syscall/unix/read.rs b/core/src/syscall/unix/read.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/read.rs rename to core/src/syscall/unix/read.rs diff --git a/open-coroutine-core/src/syscall/unix/readv.rs b/core/src/syscall/unix/readv.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/readv.rs rename to core/src/syscall/unix/readv.rs diff --git a/open-coroutine-core/src/syscall/unix/recv.rs b/core/src/syscall/unix/recv.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/recv.rs rename to core/src/syscall/unix/recv.rs diff --git a/open-coroutine-core/src/syscall/unix/recvfrom.rs b/core/src/syscall/unix/recvfrom.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/recvfrom.rs rename to core/src/syscall/unix/recvfrom.rs diff --git a/open-coroutine-core/src/syscall/unix/recvmsg.rs b/core/src/syscall/unix/recvmsg.rs similarity index 95% rename from open-coroutine-core/src/syscall/unix/recvmsg.rs rename to core/src/syscall/unix/recvmsg.rs index 084c3854..88920f27 100644 --- a/open-coroutine-core/src/syscall/unix/recvmsg.rs +++ b/core/src/syscall/unix/recvmsg.rs @@ -1,10 +1,9 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use crate::syscall::common::{is_blocking, reset_errno, set_blocking, set_non_blocking}; use libc::{msghdr, ssize_t}; use once_cell::sync::Lazy; use std::ffi::{c_int, c_void}; use std::io::{Error, ErrorKind}; -use std::time::Duration; #[must_use] pub extern "C" fn recvmsg( @@ -44,12 +43,14 @@ impl_io_uring!(IoUringRecvmsgSyscall, RecvmsgSyscall, recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t ); +#[repr(C)] #[derive(Debug, Default)] struct NioRecvmsgSyscall { inner: I, } impl RecvmsgSyscall for NioRecvmsgSyscall { + #[allow(clippy::too_many_lines)] extern "C" fn recvmsg( &self, fn_ptr: Option<&extern "C" fn(c_int, *mut msghdr, c_int) -> ssize_t>, @@ -94,7 +95,7 @@ impl RecvmsgSyscall for NioRecvmsgSyscall { let msg_iovlen = vec.len(); } else { let msg_iovlen = c_int::try_from(iov.len()).unwrap_or_else(|_| { - panic!("{} msghdr.msg_iovlen overflow", crate::constants::Syscall::recvmsg) + panic!("{} msghdr.msg_iovlen overflow", crate::common::constants::Syscall::recvmsg) }); } } @@ -133,7 +134,9 @@ impl RecvmsgSyscall for NioRecvmsgSyscall { let error_kind = Error::last_os_error().kind(); if error_kind == ErrorKind::WouldBlock { //wait read event - if EventLoops::wait_read_event(fd, Some(Duration::from_millis(10))).is_err() { + if EventLoops::wait_read_event(fd, Some(crate::common::constants::SLICE)) + .is_err() + { std::mem::forget(vec); if blocking { set_blocking(fd); diff --git a/open-coroutine-core/src/syscall/unix/select.rs b/core/src/syscall/unix/select.rs similarity index 96% rename from open-coroutine-core/src/syscall/unix/select.rs rename to core/src/syscall/unix/select.rs index 6fa8d265..94e8ea66 100644 --- a/open-coroutine-core/src/syscall/unix/select.rs +++ b/core/src/syscall/unix/select.rs @@ -1,4 +1,4 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use libc::{fd_set, timeval}; use once_cell::sync::Lazy; use std::ffi::{c_int, c_uint}; @@ -39,6 +39,7 @@ impl_facade!(SelectSyscallFacade, SelectSyscall, errorfds: *mut fd_set, timeout: *mut timeval) -> c_int ); +#[repr(C)] #[derive(Debug, Default)] struct NioSelectSyscall { inner: I, @@ -85,7 +86,7 @@ impl SelectSyscall for NioSelectSyscall { if r != 0 || t == 0 { break; } - _ = EventLoops::wait_just(Some(Duration::from_millis(u64::from(t.min(x))))); + _ = EventLoops::wait_event(Some(Duration::from_millis(u64::from(t.min(x))))); if t != c_uint::MAX { t = if t > x { t - x } else { 0 }; } diff --git a/open-coroutine-core/src/syscall/unix/send.rs b/core/src/syscall/unix/send.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/send.rs rename to core/src/syscall/unix/send.rs diff --git a/open-coroutine-core/src/syscall/unix/sendmsg.rs b/core/src/syscall/unix/sendmsg.rs similarity index 95% rename from open-coroutine-core/src/syscall/unix/sendmsg.rs rename to core/src/syscall/unix/sendmsg.rs index aa568cbb..34b66efe 100644 --- a/open-coroutine-core/src/syscall/unix/sendmsg.rs +++ b/core/src/syscall/unix/sendmsg.rs @@ -1,10 +1,9 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use crate::syscall::common::{is_blocking, reset_errno, set_blocking, set_non_blocking}; use libc::{msghdr, ssize_t}; use once_cell::sync::Lazy; use std::ffi::{c_int, c_void}; use std::io::{Error, ErrorKind}; -use std::time::Duration; #[must_use] pub extern "C" fn sendmsg( @@ -44,6 +43,7 @@ impl_io_uring!(IoUringSendmsgSyscall, SendmsgSyscall, sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t ); +#[repr(C)] #[derive(Debug, Default)] struct NioSendmsgSyscall { inner: I, @@ -94,7 +94,7 @@ impl SendmsgSyscall for NioSendmsgSyscall { let msg_iovlen = vec.len(); } else { let msg_iovlen = c_int::try_from(iov.len()).unwrap_or_else(|_| { - panic!("{} msghdr.msg_iovlen overflow", crate::constants::Syscall::recvmsg) + panic!("{} msghdr.msg_iovlen overflow", crate::common::constants::Syscall::recvmsg) }); } } @@ -127,7 +127,9 @@ impl SendmsgSyscall for NioSendmsgSyscall { let error_kind = Error::last_os_error().kind(); if error_kind == ErrorKind::WouldBlock { //wait write event - if EventLoops::wait_write_event(fd, Some(Duration::from_millis(10))).is_err() { + if EventLoops::wait_write_event(fd, Some(crate::common::constants::SLICE)) + .is_err() + { std::mem::forget(vec); if blocking { set_blocking(fd); diff --git a/open-coroutine-core/src/syscall/unix/sendto.rs b/core/src/syscall/unix/sendto.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/sendto.rs rename to core/src/syscall/unix/sendto.rs diff --git a/open-coroutine-core/src/syscall/unix/shutdown.rs b/core/src/syscall/unix/shutdown.rs similarity index 96% rename from open-coroutine-core/src/syscall/unix/shutdown.rs rename to core/src/syscall/unix/shutdown.rs index 35a50f6c..a2e86f49 100644 --- a/open-coroutine-core/src/syscall/unix/shutdown.rs +++ b/core/src/syscall/unix/shutdown.rs @@ -1,4 +1,4 @@ -use crate::net::event_loop::EventLoops; +use crate::net::EventLoops; use crate::syscall::common::set_errno; use once_cell::sync::Lazy; use std::ffi::c_int; @@ -35,6 +35,7 @@ impl_facade!(ShutdownSyscallFacade, ShutdownSyscall, shutdown(fd: c_int, how: c_ impl_io_uring!(IoUringShutdownSyscall, ShutdownSyscall, shutdown(fd: c_int, how: c_int) -> c_int); +#[repr(C)] #[derive(Debug, Default)] struct NioShutdownSyscall { inner: I, @@ -47,7 +48,7 @@ impl ShutdownSyscall for NioShutdownSyscall { fd: c_int, how: c_int, ) -> c_int { - match how { + _ = match how { libc::SHUT_RD => EventLoops::del_read_event(fd), libc::SHUT_WR => EventLoops::del_write_event(fd), libc::SHUT_RDWR => EventLoops::del_event(fd), diff --git a/open-coroutine-core/src/syscall/unix/sleep.rs b/core/src/syscall/unix/sleep.rs similarity index 71% rename from open-coroutine-core/src/syscall/unix/sleep.rs rename to core/src/syscall/unix/sleep.rs index 594e104d..32b8d525 100644 --- a/open-coroutine-core/src/syscall/unix/sleep.rs +++ b/core/src/syscall/unix/sleep.rs @@ -1,10 +1,6 @@ -use crate::common::{Current, Named}; -use crate::constants::{Syscall, SyscallState}; -use crate::net::event_loop::EventLoops; -use crate::scheduler::SchedulableCoroutine; +use crate::net::EventLoops; use crate::syscall::common::reset_errno; use once_cell::sync::Lazy; -use open_coroutine_timer::get_timeout_time; use std::ffi::c_uint; use std::time::Duration; @@ -24,6 +20,7 @@ trait SleepSyscall { impl_facade!(SleepSyscallFacade, SleepSyscall, sleep(secs: c_uint) -> c_uint); +#[repr(C)] #[derive(Debug, Copy, Clone, Default)] struct NioSleepSyscall {} @@ -34,19 +31,21 @@ impl SleepSyscall for NioSleepSyscall { secs: c_uint, ) -> c_uint { let time = Duration::from_secs(u64::from(secs)); - if let Some(co) = SchedulableCoroutine::current() { - let syscall = Syscall::sleep; - let new_state = SyscallState::Suspend(get_timeout_time(time)); + if let Some(co) = crate::scheduler::SchedulableCoroutine::current() { + let syscall = crate::common::constants::Syscall::sleep; + let new_state = crate::common::constants::SyscallState::Suspend( + crate::common::get_timeout_time(time), + ); if co.syscall((), syscall, new_state).is_err() { crate::error!( "{} change to syscall {} {} failed !", - co.get_name(), + co.name(), syscall, new_state ); } } - _ = EventLoops::wait_just(Some(time)); + _ = EventLoops::wait_event(Some(time)); reset_errno(); 0 } diff --git a/open-coroutine-core/src/syscall/unix/socket.rs b/core/src/syscall/unix/socket.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/socket.rs rename to core/src/syscall/unix/socket.rs diff --git a/open-coroutine-core/src/syscall/unix/usleep.rs b/core/src/syscall/unix/usleep.rs similarity index 74% rename from open-coroutine-core/src/syscall/unix/usleep.rs rename to core/src/syscall/unix/usleep.rs index 44cfbe14..d729d86e 100644 --- a/open-coroutine-core/src/syscall/unix/usleep.rs +++ b/core/src/syscall/unix/usleep.rs @@ -1,10 +1,6 @@ -use crate::common::{Current, Named}; -use crate::constants::{Syscall, SyscallState}; -use crate::net::event_loop::EventLoops; -use crate::scheduler::SchedulableCoroutine; +use crate::net::EventLoops; use crate::syscall::common::reset_errno; use once_cell::sync::Lazy; -use open_coroutine_timer::get_timeout_time; use std::ffi::{c_int, c_uint}; use std::time::Duration; @@ -29,6 +25,7 @@ impl_facade!(UsleepSyscallFacade, UsleepSyscall, usleep(microseconds: c_uint) -> c_int ); +#[repr(C)] #[derive(Debug, Copy, Clone, Default)] struct NioUsleepSyscall {} @@ -42,19 +39,21 @@ impl UsleepSyscall for NioUsleepSyscall { Some(v) => Duration::from_nanos(v), None => Duration::MAX, }; - if let Some(co) = SchedulableCoroutine::current() { - let syscall = Syscall::usleep; - let new_state = SyscallState::Suspend(get_timeout_time(time)); + if let Some(co) = crate::scheduler::SchedulableCoroutine::current() { + let syscall = crate::common::constants::Syscall::usleep; + let new_state = crate::common::constants::SyscallState::Suspend( + crate::common::get_timeout_time(time), + ); if co.syscall((), syscall, new_state).is_err() { crate::error!( "{} change to syscall {} {} failed !", - co.get_name(), + co.name(), syscall, new_state ); } } - _ = EventLoops::wait_just(Some(time)); + _ = EventLoops::wait_event(Some(time)); reset_errno(); 0 } diff --git a/open-coroutine-core/src/syscall/unix/write.rs b/core/src/syscall/unix/write.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/write.rs rename to core/src/syscall/unix/write.rs diff --git a/open-coroutine-core/src/syscall/unix/writev.rs b/core/src/syscall/unix/writev.rs similarity index 100% rename from open-coroutine-core/src/syscall/unix/writev.rs rename to core/src/syscall/unix/writev.rs diff --git a/core/src/syscall/windows/Sleep.rs b/core/src/syscall/windows/Sleep.rs new file mode 100644 index 00000000..17dc5c96 --- /dev/null +++ b/core/src/syscall/windows/Sleep.rs @@ -0,0 +1,41 @@ +use crate::net::EventLoops; +use once_cell::sync::Lazy; +use std::time::Duration; + +pub extern "system" fn Sleep(fn_ptr: Option<&extern "system" fn(u32)>, dw_milliseconds: u32) { + static CHAIN: Lazy> = Lazy::new(Default::default); + CHAIN.Sleep(fn_ptr, dw_milliseconds); +} + +trait SleepSyscall { + extern "system" fn Sleep(&self, fn_ptr: Option<&extern "system" fn(u32)>, dw_milliseconds: u32); +} + +impl_facade!(SleepSyscallFacade, SleepSyscall, + Sleep(dw_milliseconds: u32) -> () +); + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +struct NioSleepSyscall {} + +impl SleepSyscall for NioSleepSyscall { + extern "system" fn Sleep(&self, _: Option<&extern "system" fn(u32)>, dw_milliseconds: u32) { + let time = Duration::from_millis(u64::from(dw_milliseconds)); + if let Some(co) = crate::scheduler::SchedulableCoroutine::current() { + let syscall = crate::common::constants::Syscall::Sleep; + let new_state = crate::common::constants::SyscallState::Suspend( + crate::common::get_timeout_time(time), + ); + if co.syscall((), syscall, new_state).is_err() { + crate::error!( + "{} change to syscall {} {} failed !", + co.name(), + syscall, + new_state + ); + } + } + _ = EventLoops::wait_event(Some(time)); + } +} diff --git a/core/src/syscall/windows/WSARecv.rs b/core/src/syscall/windows/WSARecv.rs new file mode 100644 index 00000000..e6f1eb26 --- /dev/null +++ b/core/src/syscall/windows/WSARecv.rs @@ -0,0 +1,99 @@ +use once_cell::sync::Lazy; +use std::ffi::{c_int, c_uint}; +use windows_sys::Win32::Networking::WinSock::{LPWSAOVERLAPPED_COMPLETION_ROUTINE, SOCKET, WSABUF}; +use windows_sys::Win32::System::IO::OVERLAPPED; + +#[must_use] +pub extern "system" fn WSARecv( + fn_ptr: Option< + &extern "system" fn( + SOCKET, + *const WSABUF, + c_uint, + *mut c_uint, + *mut c_uint, + *mut OVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int, + >, + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags: *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.WSARecv( + fn_ptr, + fd, + buf, + dwbuffercount, + lpnumberofbytesrecvd, + lpflags, + lpoverlapped, + lpcompletionroutine, + ) +} + +trait WSARecvSyscall { + extern "system" fn WSARecv( + &self, + fn_ptr: Option< + &extern "system" fn( + SOCKET, + *const WSABUF, + c_uint, + *mut c_uint, + *mut c_uint, + *mut OVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int, + >, + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags: *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int; +} + +impl_facade!(WSARecvSyscallFacade, WSARecvSyscall, + WSARecv( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags : *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); + +impl_nio_read_iovec!(NioWSARecvSyscall, WSARecvSyscall, + WSARecv( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags : *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); + +impl_raw!(RawWSARecvSyscall, WSARecvSyscall, windows_sys::Win32::Networking::WinSock, + WSARecv( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags : *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); diff --git a/core/src/syscall/windows/WSASend.rs b/core/src/syscall/windows/WSASend.rs new file mode 100644 index 00000000..c0675695 --- /dev/null +++ b/core/src/syscall/windows/WSASend.rs @@ -0,0 +1,99 @@ +use once_cell::sync::Lazy; +use std::ffi::{c_int, c_uint}; +use windows_sys::Win32::Networking::WinSock::{LPWSAOVERLAPPED_COMPLETION_ROUTINE, SOCKET, WSABUF}; +use windows_sys::Win32::System::IO::OVERLAPPED; + +#[must_use] +pub extern "system" fn WSASend( + fn_ptr: Option< + &extern "system" fn( + SOCKET, + *const WSABUF, + c_uint, + *mut c_uint, + c_uint, + *mut OVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int, + >, + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags: c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.WSASend( + fn_ptr, + fd, + buf, + dwbuffercount, + lpnumberofbytesrecvd, + dwflags, + lpoverlapped, + lpcompletionroutine, + ) +} + +trait WSARecvSyscall { + extern "system" fn WSASend( + &self, + fn_ptr: Option< + &extern "system" fn( + SOCKET, + *const WSABUF, + c_uint, + *mut c_uint, + c_uint, + *mut OVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int, + >, + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags: c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, + ) -> c_int; +} + +impl_facade!(WSARecvSyscallFacade, WSARecvSyscall, + WSASend( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags : c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); + +impl_nio_write_iovec!(NioWSARecvSyscall, WSARecvSyscall, + WSASend( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags : c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); + +impl_raw!(RawWSARecvSyscall, WSARecvSyscall, windows_sys::Win32::Networking::WinSock, + WSASend( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags : c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int +); diff --git a/core/src/syscall/windows/WSASocketW.rs b/core/src/syscall/windows/WSASocketW.rs new file mode 100644 index 00000000..fe0022ef --- /dev/null +++ b/core/src/syscall/windows/WSASocketW.rs @@ -0,0 +1,72 @@ +use once_cell::sync::Lazy; +use std::ffi::{c_int, c_uint}; +use windows_sys::Win32::Networking::WinSock::{ + IPPROTO, SOCKET, WINSOCK_SOCKET_TYPE, WSAPROTOCOL_INFOW, +}; + +#[must_use] +pub extern "system" fn WSASocketW( + fn_ptr: Option< + &extern "system" fn( + c_int, + WINSOCK_SOCKET_TYPE, + IPPROTO, + *const WSAPROTOCOL_INFOW, + c_uint, + c_uint, + ) -> SOCKET, + >, + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + lpprotocolinfo: *const WSAPROTOCOL_INFOW, + g: c_uint, + dw_flags: c_uint, +) -> SOCKET { + static CHAIN: Lazy> = Lazy::new(Default::default); + CHAIN.WSASocketW(fn_ptr, domain, ty, protocol, lpprotocolinfo, g, dw_flags) +} + +trait WSASocketWSyscall { + extern "system" fn WSASocketW( + &self, + fn_ptr: Option< + &extern "system" fn( + c_int, + WINSOCK_SOCKET_TYPE, + IPPROTO, + *const WSAPROTOCOL_INFOW, + c_uint, + c_uint, + ) -> SOCKET, + >, + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + lpprotocolinfo: *const WSAPROTOCOL_INFOW, + g: c_uint, + dw_flags: c_uint, + ) -> SOCKET; +} + +impl_facade!(WSASocketWSyscallFacade, WSASocketWSyscall, + WSASocketW( + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + lpprotocolinfo: *const WSAPROTOCOL_INFOW, + g: c_uint, + dw_flags: c_uint + ) -> SOCKET +); + +impl_raw!(RawWSASocketWSyscall, WSASocketWSyscall, windows_sys::Win32::Networking::WinSock, + WSASocketW( + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + lpprotocolinfo: *const WSAPROTOCOL_INFOW, + g: c_uint, + dw_flags: c_uint + ) -> SOCKET +); diff --git a/core/src/syscall/windows/accept.rs b/core/src/syscall/windows/accept.rs new file mode 100644 index 00000000..dbf1dd60 --- /dev/null +++ b/core/src/syscall/windows/accept.rs @@ -0,0 +1,37 @@ +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::Win32::Networking::WinSock::{SOCKADDR, SOCKET}; + +#[must_use] +pub extern "system" fn accept( + fn_ptr: Option<&extern "system" fn(SOCKET, *mut SOCKADDR, *mut c_int) -> SOCKET>, + fd: SOCKET, + address: *mut SOCKADDR, + address_len: *mut c_int, +) -> SOCKET { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.accept(fn_ptr, fd, address, address_len) +} + +trait AcceptSyscall { + extern "system" fn accept( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, *mut SOCKADDR, *mut c_int) -> SOCKET>, + fd: SOCKET, + address: *mut SOCKADDR, + address_len: *mut c_int, + ) -> SOCKET; +} + +impl_facade!(AcceptSyscallFacade, AcceptSyscall, + accept(fd: SOCKET, address: *mut SOCKADDR, address_len: *mut c_int) -> SOCKET +); + +impl_nio_read!(NioAcceptSyscall, AcceptSyscall, + accept(fd: SOCKET, address: *mut SOCKADDR, address_len: *mut c_int) -> SOCKET +); + +impl_raw!(RawAcceptSyscall, AcceptSyscall, windows_sys::Win32::Networking::WinSock, + accept(fd: SOCKET, address: *mut SOCKADDR, address_len: *mut c_int) -> SOCKET +); diff --git a/core/src/syscall/windows/ioctlsocket.rs b/core/src/syscall/windows/ioctlsocket.rs new file mode 100644 index 00000000..a464f14d --- /dev/null +++ b/core/src/syscall/windows/ioctlsocket.rs @@ -0,0 +1,93 @@ +use crate::common::constants::{CoroutineState, Syscall, SyscallState}; +use crate::scheduler::SchedulableCoroutine; +use crate::syscall::windows::NON_BLOCKING; +use crate::{error, info}; +use once_cell::sync::Lazy; +use std::ffi::{c_int, c_uint}; +use windows_sys::Win32::Networking::WinSock::SOCKET; + +#[must_use] +pub extern "system" fn ioctlsocket( + fn_ptr: Option<&extern "system" fn(SOCKET, c_int, *mut c_uint) -> c_int>, + fd: SOCKET, + cmd: c_int, + argp: *mut c_uint, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.ioctlsocket(fn_ptr, fd, cmd, argp) +} + +trait IoctlsocketSyscall { + extern "system" fn ioctlsocket( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, c_int, *mut c_uint) -> c_int>, + fd: SOCKET, + cmd: c_int, + argp: *mut c_uint, + ) -> c_int; +} + +#[repr(C)] +#[derive(Debug, Default)] +struct IoctlsocketSyscallFacade { + inner: I, +} + +impl IoctlsocketSyscall for IoctlsocketSyscallFacade { + extern "system" fn ioctlsocket( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, c_int, *mut c_uint) -> c_int>, + fd: SOCKET, + cmd: c_int, + argp: *mut c_uint, + ) -> c_int { + let syscall = Syscall::ioctlsocket; + info!("enter syscall {}", syscall); + if let Some(co) = SchedulableCoroutine::current() { + _ = co.syscall((), syscall, SyscallState::Executing); + } + let r = self.inner.ioctlsocket(fn_ptr, fd, cmd, argp); + if let Some(co) = SchedulableCoroutine::current() { + if let CoroutineState::SystemCall((), Syscall::ioctlsocket, SyscallState::Executing) = + co.state() + { + if co.running().is_err() { + error!("{} change to running state failed !", co.name()); + } + } + } + info!("exit syscall {}", syscall); + r + } +} + +#[repr(C)] +#[derive(Debug, Default)] +struct NioIoctlsocketSyscall { + inner: I, +} + +impl IoctlsocketSyscall for NioIoctlsocketSyscall { + extern "system" fn ioctlsocket( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, c_int, *mut c_uint) -> c_int>, + fd: SOCKET, + cmd: c_int, + argp: *mut c_uint, + ) -> c_int { + let r = self.inner.ioctlsocket(fn_ptr, fd, cmd, argp); + if 0 == r { + if 0 == unsafe { *argp } { + _ = NON_BLOCKING.remove(&fd); + } else { + _ = NON_BLOCKING.insert(fd); + } + } + r + } +} + +impl_raw!(RawIoctlsocketSyscall, IoctlsocketSyscall, windows_sys::Win32::Networking::WinSock, + ioctlsocket(fd: SOCKET, cmd: c_int, argp: *mut c_uint) -> c_int +); diff --git a/core/src/syscall/windows/listen.rs b/core/src/syscall/windows/listen.rs new file mode 100644 index 00000000..ec52b2d1 --- /dev/null +++ b/core/src/syscall/windows/listen.rs @@ -0,0 +1,27 @@ +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::Win32::Networking::WinSock::SOCKET; + +pub extern "system" fn listen( + fn_ptr: Option<&extern "system" fn(SOCKET, c_int) -> c_int>, + fd: SOCKET, + backlog: c_int, +) -> c_int { + static CHAIN: Lazy> = Lazy::new(Default::default); + CHAIN.listen(fn_ptr, fd, backlog) +} + +trait ListenSyscall { + extern "system" fn listen( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, c_int) -> c_int>, + fd: SOCKET, + backlog: c_int, + ) -> c_int; +} + +impl_facade!(ListenSyscallFacade, ListenSyscall, listen(fd: SOCKET, backlog: c_int) -> c_int); + +impl_raw!(RawListenSyscall, ListenSyscall, windows_sys::Win32::Networking::WinSock, + listen(fd: SOCKET, backlog: c_int) -> c_int +); diff --git a/open-coroutine-core/src/syscall/unix/mod.rs b/core/src/syscall/windows/mod.rs similarity index 72% rename from open-coroutine-core/src/syscall/unix/mod.rs rename to core/src/syscall/windows/mod.rs index 05f9f664..b406c00b 100644 --- a/open-coroutine-core/src/syscall/unix/mod.rs +++ b/core/src/syscall/windows/mod.rs @@ -1,61 +1,46 @@ +use dashmap::DashSet; +use once_cell::sync::Lazy; +use windows_sys::Win32::Networking::WinSock::SOCKET; + pub use accept::accept; -#[cfg(target_os = "linux")] -pub use accept4::accept4; -pub use close::close; -pub use connect::connect; +pub use ioctlsocket::ioctlsocket; pub use listen::listen; -pub use nanosleep::nanosleep; -pub use poll::poll; -pub use pread::pread; -pub use preadv::preadv; -pub use pwrite::pwrite; -pub use pwritev::pwritev; -pub use read::read; -pub use readv::readv; pub use recv::recv; -pub use recvfrom::recvfrom; -pub use recvmsg::recvmsg; -pub use select::select; pub use send::send; -pub use sendmsg::sendmsg; -pub use sendto::sendto; pub use shutdown::shutdown; -pub use sleep::sleep; pub use socket::socket; -pub use usleep::usleep; -pub use write::write; -pub use writev::writev; +pub use Sleep::Sleep; +pub use WSARecv::WSARecv; +pub use WSASend::WSASend; +pub use WSASocketW::WSASocketW; macro_rules! impl_facade { ( $struct_name:ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($($arg_type),*) -> $result>, $($arg: $arg_type),* ) -> $result { - use $crate::constants::{Syscall, SyscallState}; - use $crate::common::{Current, Named}; - use $crate::scheduler::SchedulableCoroutine; - - let syscall = Syscall::$syscall; + let syscall = $crate::common::constants::Syscall::$syscall; $crate::info!("enter syscall {}", syscall); - if let Some(co) = SchedulableCoroutine::current() { - let new_state = SyscallState::Executing; + if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() { + let new_state = $crate::common::constants::SyscallState::Executing; if co.syscall((), syscall, new_state).is_err() { $crate::error!("{} change to syscall {} {} failed !", - co.get_name(), syscall, new_state); + co.name(), syscall, new_state); } } let r = self.inner.$syscall(fn_ptr, $($arg, )*); - if let Some(co) = SchedulableCoroutine::current() { + if let Some(co) = $crate::scheduler::SchedulableCoroutine::current() { if co.running().is_err() { - $crate::error!("{} change to running state failed !", co.get_name()); + $crate::error!("{} change to running state failed !", co.name()); } } $crate::info!("exit syscall {}", syscall); @@ -65,39 +50,18 @@ macro_rules! impl_facade { } } -macro_rules! impl_io_uring { - ( $struct_name:ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { - #[cfg(all(target_os = "linux", feature = "io_uring"))] - #[derive(Debug, Default)] - struct $struct_name { - inner: I, - } - - #[cfg(all(target_os = "linux", feature = "io_uring"))] - impl $trait_name for $struct_name { - extern "C" fn $syscall( - &self, - fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, - $($arg: $arg_type),* - ) -> $result { - $crate::net::event_loop::EventLoops::$syscall($($arg, )*) - .unwrap_or_else(|_| self.inner.$syscall(fn_ptr, $($arg, )*)) - } - } - } -} - macro_rules! impl_nio_read { ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($fd_type, $($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($fd_type, $($arg_type),*) -> $result>, $fd: $fd_type, $($arg: $arg_type),* ) -> $result { @@ -108,16 +72,16 @@ macro_rules! impl_nio_read { let mut r; loop { r = self.inner.$syscall(fn_ptr, $fd, $($arg, )*); - if r != -1 { + if r != -1 as _ { $crate::syscall::common::reset_errno(); break; } let error_kind = std::io::Error::last_os_error().kind(); if error_kind == std::io::ErrorKind::WouldBlock { //wait read event - if $crate::net::event_loop::EventLoops::wait_read_event( - $fd, - Some(std::time::Duration::from_millis(10)), + if $crate::net::EventLoops::wait_read_event( + $fd as _, + Some($crate::common::constants::SLICE), ).is_err() { break; } @@ -137,15 +101,16 @@ macro_rules! impl_nio_read { macro_rules! impl_nio_read_buf { ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $buf: ident : $buf_type: ty, $len: ident : $len_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, $fd: $fd_type, $buf: $buf_type, $len: $len_type, @@ -161,24 +126,24 @@ macro_rules! impl_nio_read_buf { r = self.inner.$syscall( fn_ptr, $fd, - ($buf as usize + received) as *mut std::ffi::c_void, + ($buf as usize + received as usize) as windows_sys::core::PSTR, $len - received, $($arg, )* ); if r != -1 { $crate::syscall::common::reset_errno(); - received += r as size_t; + received += r; if received >= $len || r == 0 { - r = received as ssize_t; + r = received; break; } } let error_kind = std::io::Error::last_os_error().kind(); if error_kind == std::io::ErrorKind::WouldBlock { //wait read event - if $crate::net::event_loop::EventLoops::wait_read_event( - $fd, - Some(std::time::Duration::from_millis(10)), + if $crate::net::EventLoops::wait_read_event( + $fd as _, + Some($crate::common::constants::SLICE), ) .is_err() { @@ -200,15 +165,16 @@ macro_rules! impl_nio_read_buf { macro_rules! impl_nio_read_iovec { ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $iov: ident : $iov_type: ty, $iovcnt: ident : $iovcnt_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, $fd: $fd_type, $iov: $iov_type, $iovcnt: $iovcnt_type, @@ -225,7 +191,7 @@ macro_rules! impl_nio_read_iovec { let mut index = 0; for iovec in &vec { let mut offset = received.saturating_sub(length); - length += iovec.iov_len; + length += iovec.len as usize; if received > length { index += 1; continue; @@ -236,17 +202,17 @@ macro_rules! impl_nio_read_iovec { } while received < length { if 0 != offset { - arg[0] = libc::iovec { - iov_base: (arg[0].iov_base as usize + offset) as *mut std::ffi::c_void, - iov_len: arg[0].iov_len - offset, + arg[0] = windows_sys::Win32::Networking::WinSock::WSABUF { + buf: (arg[0].buf as usize + offset) as windows_sys::core::PSTR, + len: arg[0].len - offset as u32, }; } r = self.inner.$syscall( fn_ptr, $fd, arg.as_ptr(), - c_int::try_from(arg.len()).unwrap_or_else(|_| { - panic!("{} iovcnt overflow", $crate::constants::Syscall::$syscall) + std::ffi::c_uint::try_from(arg.len()).unwrap_or_else(|_| { + panic!("{} iovcnt overflow", $crate::common::constants::Syscall::$syscall) }), $($arg, )* ); @@ -260,7 +226,7 @@ macro_rules! impl_nio_read_iovec { $crate::syscall::common::reset_errno(); received += r as usize; if received >= length { - r = received as ssize_t; + r = received.try_into().expect("overflow"); break; } offset = received.saturating_sub(length); @@ -268,9 +234,9 @@ macro_rules! impl_nio_read_iovec { let error_kind = std::io::Error::last_os_error().kind(); if error_kind == std::io::ErrorKind::WouldBlock { //wait read event - if $crate::net::event_loop::EventLoops::wait_read_event( - $fd, - Some(std::time::Duration::from_millis(10)) + if $crate::net::EventLoops::wait_read_event( + $fd as _, + Some($crate::common::constants::SLICE) ).is_err() { std::mem::forget(vec); if blocking { @@ -303,15 +269,16 @@ macro_rules! impl_nio_read_iovec { macro_rules! impl_nio_write_buf { ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $buf: ident : $buf_type: ty, $len: ident : $len_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($fd_type, $buf_type, $len_type, $($arg_type),*) -> $result>, $fd: $fd_type, $buf: $buf_type, $len: $len_type, @@ -327,24 +294,24 @@ macro_rules! impl_nio_write_buf { r = self.inner.$syscall( fn_ptr, $fd, - ($buf as usize + sent) as *const std::ffi::c_void, + ($buf as usize + sent as usize) as windows_sys::core::PSTR, $len - sent, $($arg, )* ); if r != -1 { $crate::syscall::common::reset_errno(); - sent += r as size_t; + sent += r; if sent >= $len { - r = sent as ssize_t; + r = sent; break; } } let error_kind = std::io::Error::last_os_error().kind(); if error_kind == std::io::ErrorKind::WouldBlock { //wait write event - if $crate::net::event_loop::EventLoops::wait_write_event( - $fd, - Some(std::time::Duration::from_millis(10)), + if $crate::net::EventLoops::wait_write_event( + $fd as _, + Some($crate::common::constants::SLICE), ) .is_err() { @@ -366,15 +333,16 @@ macro_rules! impl_nio_write_buf { macro_rules! impl_nio_write_iovec { ( $struct_name:ident, $trait_name: ident, $syscall: ident($fd: ident : $fd_type: ty, $iov: ident : $iov_type: ty, $iovcnt: ident : $iovcnt_type: ty, $($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Default)] struct $struct_name { inner: I, } impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($fd_type, $iov_type, $iovcnt_type, $($arg_type),*) -> $result>, $fd: $fd_type, $iov: $iov_type, $iovcnt: $iovcnt_type, @@ -391,7 +359,7 @@ macro_rules! impl_nio_write_iovec { let mut index = 0; for iovec in &vec { let mut offset = sent.saturating_sub(length); - length += iovec.iov_len; + length += iovec.len as usize; if sent > length { index += 1; continue; @@ -402,17 +370,17 @@ macro_rules! impl_nio_write_iovec { } while sent < length { if 0 != offset { - arg[0] = libc::iovec { - iov_base: (arg[0].iov_base as usize + offset) as *mut std::ffi::c_void, - iov_len: arg[0].iov_len - offset, + arg[0] = windows_sys::Win32::Networking::WinSock::WSABUF { + buf: (arg[0].buf as usize + offset) as windows_sys::core::PSTR, + len: arg[0].len - offset as u32, }; } r = self.inner.$syscall( fn_ptr, $fd, arg.as_ptr(), - c_int::try_from(arg.len()).unwrap_or_else(|_| { - panic!("{} iovcnt overflow", $crate::constants::Syscall::$syscall) + std::ffi::c_uint::try_from(arg.len()).unwrap_or_else(|_| { + panic!("{} iovcnt overflow", $crate::common::constants::Syscall::$syscall) }), $($arg, )* ); @@ -420,7 +388,7 @@ macro_rules! impl_nio_write_iovec { $crate::syscall::common::reset_errno(); sent += r as usize; if sent >= length { - r = sent as ssize_t; + r = sent.try_into().expect("overflow"); break; } offset = sent.saturating_sub(length); @@ -428,9 +396,9 @@ macro_rules! impl_nio_write_iovec { let error_kind = std::io::Error::last_os_error().kind(); if error_kind == std::io::ErrorKind::WouldBlock { //wait write event - if $crate::net::event_loop::EventLoops::wait_write_event( - $fd, - Some(std::time::Duration::from_millis(10)) + if $crate::net::EventLoops::wait_write_event( + $fd as _, + Some($crate::common::constants::SLICE) ).is_err() { std::mem::forget(vec); if blocking { @@ -461,50 +429,78 @@ macro_rules! impl_nio_write_iovec { } macro_rules! impl_raw { - ( $struct_name: ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + ( $struct_name: ident, $trait_name: ident, $($mod_name: ident)::*, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[repr(C)] #[derive(Debug, Copy, Clone, Default)] struct $struct_name {} impl $trait_name for $struct_name { - extern "C" fn $syscall( + extern "system" fn $syscall( &self, - fn_ptr: Option<&extern "C" fn($($arg_type),*) -> $result>, + fn_ptr: Option<&extern "system" fn($($arg_type),*) -> $result>, $($arg: $arg_type),* ) -> $result { if let Some(f) = fn_ptr { (f)($($arg),*) } else { - unsafe { libc::$syscall($($arg),*) } + unsafe { $($mod_name)::*::$syscall($($arg),*) } } } } } } +mod Sleep; +mod WSARecv; +mod WSASend; +mod WSASocketW; mod accept; -#[cfg(target_os = "linux")] -mod accept4; -mod close; -mod connect; +mod ioctlsocket; mod listen; -mod nanosleep; -mod poll; -mod pread; -mod preadv; -mod pwrite; -mod pwritev; -mod read; -mod readv; mod recv; -mod recvfrom; -mod recvmsg; -mod select; mod send; -mod sendmsg; -mod sendto; mod shutdown; -mod sleep; mod socket; -mod usleep; -mod write; -mod writev; + +static NON_BLOCKING: Lazy> = Lazy::new(Default::default); + +pub extern "C" fn set_errno(errno: windows_sys::Win32::Foundation::WIN32_ERROR) { + unsafe { windows_sys::Win32::Foundation::SetLastError(errno) } +} + +/// # Panics +/// if set fails. +pub extern "C" fn set_non_blocking(fd: SOCKET) { + assert!(set_non_blocking_flag(fd, true), "set_non_blocking failed !"); +} + +/// # Panics +/// if set fails. +pub extern "C" fn set_blocking(fd: SOCKET) { + assert!(set_non_blocking_flag(fd, false), "set_blocking failed !"); +} + +extern "C" fn set_non_blocking_flag(fd: SOCKET, on: bool) -> bool { + let non_blocking = is_non_blocking(fd); + if non_blocking == on { + return true; + } + let mut argp = on.into(); + unsafe { + windows_sys::Win32::Networking::WinSock::ioctlsocket( + fd, + windows_sys::Win32::Networking::WinSock::FIONBIO, + &mut argp, + ) == 0 + } +} + +#[must_use] +pub extern "C" fn is_blocking(fd: SOCKET) -> bool { + !is_non_blocking(fd) +} + +#[must_use] +pub extern "C" fn is_non_blocking(fd: SOCKET) -> bool { + NON_BLOCKING.contains(&fd) +} diff --git a/core/src/syscall/windows/recv.rs b/core/src/syscall/windows/recv.rs new file mode 100644 index 00000000..b9d822f7 --- /dev/null +++ b/core/src/syscall/windows/recv.rs @@ -0,0 +1,40 @@ +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::core::PSTR; +use windows_sys::Win32::Networking::WinSock::{SEND_RECV_FLAGS, SOCKET}; + +#[must_use] +pub extern "system" fn recv( + fn_ptr: Option<&extern "system" fn(SOCKET, PSTR, c_int, SEND_RECV_FLAGS) -> c_int>, + fd: SOCKET, + buf: PSTR, + len: c_int, + flags: SEND_RECV_FLAGS, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.recv(fn_ptr, fd, buf, len, flags) +} + +trait RecvSyscall { + extern "system" fn recv( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, PSTR, c_int, SEND_RECV_FLAGS) -> c_int>, + fd: SOCKET, + buf: PSTR, + len: c_int, + flags: SEND_RECV_FLAGS, + ) -> c_int; +} + +impl_facade!(RecvSyscallFacade, RecvSyscall, + recv(fd: SOCKET, buf: PSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); + +impl_nio_read_buf!(NioRecvSyscall, RecvSyscall, + recv(fd: SOCKET, buf: PSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); + +impl_raw!(RawRecvSyscall, RecvSyscall, windows_sys::Win32::Networking::WinSock, + recv(fd: SOCKET, buf: PSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); diff --git a/core/src/syscall/windows/send.rs b/core/src/syscall/windows/send.rs new file mode 100644 index 00000000..8b87be11 --- /dev/null +++ b/core/src/syscall/windows/send.rs @@ -0,0 +1,40 @@ +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::core::PCSTR; +use windows_sys::Win32::Networking::WinSock::{SEND_RECV_FLAGS, SOCKET}; + +#[must_use] +pub extern "system" fn send( + fn_ptr: Option<&extern "system" fn(SOCKET, PCSTR, c_int, SEND_RECV_FLAGS) -> c_int>, + fd: SOCKET, + buf: PCSTR, + len: c_int, + flags: SEND_RECV_FLAGS, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.send(fn_ptr, fd, buf, len, flags) +} + +trait SendSyscall { + extern "system" fn send( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, PCSTR, c_int, SEND_RECV_FLAGS) -> c_int>, + fd: SOCKET, + buf: PCSTR, + len: c_int, + flags: SEND_RECV_FLAGS, + ) -> c_int; +} + +impl_facade!(SendSyscallFacade, SendSyscall, + send(fd: SOCKET, buf: PCSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); + +impl_nio_write_buf!(NioSendSyscall, SendSyscall, + send(fd: SOCKET, buf: PCSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); + +impl_raw!(RawSendSyscall, SendSyscall, windows_sys::Win32::Networking::WinSock, + send(fd: SOCKET, buf: PCSTR, len: c_int, flags: SEND_RECV_FLAGS) -> c_int +); diff --git a/core/src/syscall/windows/shutdown.rs b/core/src/syscall/windows/shutdown.rs new file mode 100644 index 00000000..a10a01f2 --- /dev/null +++ b/core/src/syscall/windows/shutdown.rs @@ -0,0 +1,62 @@ +use crate::net::EventLoops; +use crate::syscall::common::set_errno; +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::Win32::Networking::WinSock::{SOCKET, WINSOCK_SHUTDOWN_HOW}; + +#[must_use] +pub extern "system" fn shutdown( + fn_ptr: Option<&extern "system" fn(SOCKET, WINSOCK_SHUTDOWN_HOW) -> c_int>, + fd: SOCKET, + how: WINSOCK_SHUTDOWN_HOW, +) -> c_int { + static CHAIN: Lazy>> = + Lazy::new(Default::default); + CHAIN.shutdown(fn_ptr, fd, how) +} + +trait ShutdownSyscall { + extern "system" fn shutdown( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, WINSOCK_SHUTDOWN_HOW) -> c_int>, + fd: SOCKET, + how: WINSOCK_SHUTDOWN_HOW, + ) -> c_int; +} + +impl_facade!(ShutdownSyscallFacade, ShutdownSyscall, shutdown(fd: SOCKET, how: WINSOCK_SHUTDOWN_HOW) -> c_int); + +#[repr(C)] +#[derive(Debug, Default)] +struct NioShutdownSyscall { + inner: I, +} + +impl ShutdownSyscall for NioShutdownSyscall { + extern "system" fn shutdown( + &self, + fn_ptr: Option<&extern "system" fn(SOCKET, WINSOCK_SHUTDOWN_HOW) -> c_int>, + fd: SOCKET, + how: WINSOCK_SHUTDOWN_HOW, + ) -> c_int { + { + let fd = fd as _; + _ = match how { + windows_sys::Win32::Networking::WinSock::SD_RECEIVE => { + EventLoops::del_read_event(fd) + } + windows_sys::Win32::Networking::WinSock::SD_SEND => EventLoops::del_write_event(fd), + windows_sys::Win32::Networking::WinSock::SD_BOTH => EventLoops::del_event(fd), + _ => { + set_errno(windows_sys::Win32::Networking::WinSock::WSAEINVAL as _); + return -1; + } + }; + } + self.inner.shutdown(fn_ptr, fd, how) + } +} + +impl_raw!(RawShutdownSyscall, ShutdownSyscall, windows_sys::Win32::Networking::WinSock, + shutdown(fd: SOCKET, how: WINSOCK_SHUTDOWN_HOW) -> c_int +); diff --git a/core/src/syscall/windows/socket.rs b/core/src/syscall/windows/socket.rs new file mode 100644 index 00000000..aff69705 --- /dev/null +++ b/core/src/syscall/windows/socket.rs @@ -0,0 +1,32 @@ +use once_cell::sync::Lazy; +use std::ffi::c_int; +use windows_sys::Win32::Networking::WinSock::{IPPROTO, SOCKET, WINSOCK_SOCKET_TYPE}; + +#[must_use] +pub extern "system" fn socket( + fn_ptr: Option<&extern "system" fn(c_int, WINSOCK_SOCKET_TYPE, IPPROTO) -> SOCKET>, + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, +) -> SOCKET { + static CHAIN: Lazy> = Lazy::new(Default::default); + CHAIN.socket(fn_ptr, domain, ty, protocol) +} + +trait SocketSyscall { + extern "system" fn socket( + &self, + fn_ptr: Option<&extern "system" fn(c_int, WINSOCK_SOCKET_TYPE, IPPROTO) -> SOCKET>, + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + ) -> SOCKET; +} + +impl_facade!(SocketSyscallFacade, SocketSyscall, + socket(domain: c_int, ty: WINSOCK_SOCKET_TYPE, protocol: IPPROTO) -> SOCKET +); + +impl_raw!(RawSocketSyscall, SocketSyscall, windows_sys::Win32::Networking::WinSock, + socket(domain: c_int, ty: WINSOCK_SOCKET_TYPE, protocol: IPPROTO) -> SOCKET +); diff --git a/core/tests/co_pool.rs b/core/tests/co_pool.rs new file mode 100644 index 00000000..081c2307 --- /dev/null +++ b/core/tests/co_pool.rs @@ -0,0 +1,71 @@ +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn co_pool_basic() -> std::io::Result<()> { + let task_name = "test_simple"; + let mut pool = open_coroutine_core::co_pool::CoroutinePool::default(); + pool.set_max_size(1); + assert!(pool.is_empty()); + _ = pool.submit_task( + Some(String::from("test_panic")), + |_| panic!("test panic, just ignore it"), + None, + )?; + assert!(!pool.is_empty()); + pool.submit_task( + Some(String::from(task_name)), + |_| { + println!("2"); + Some(2) + }, + None, + )?; + pool.try_schedule_task() +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn co_pool_suspend() -> std::io::Result<()> { + let mut pool = open_coroutine_core::co_pool::CoroutinePool::default(); + pool.set_max_size(2); + _ = pool.submit_task( + None, + |param| { + println!("[coroutine] delay"); + if let Some(suspender) = open_coroutine_core::scheduler::SchedulableSuspender::current() + { + suspender.delay(std::time::Duration::from_millis(100)); + } + println!("[coroutine] back"); + param + }, + None, + )?; + _ = pool.submit_task( + None, + |_| { + println!("middle"); + Some(1) + }, + None, + )?; + pool.try_schedule_task()?; + std::thread::sleep(std::time::Duration::from_millis(200)); + pool.try_schedule_task() +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn co_pool_stop() -> std::io::Result<()> { + let pool = open_coroutine_core::co_pool::CoroutinePool::default(); + pool.set_max_size(1); + _ = pool.submit_task(None, |_| panic!("test panic, just ignore it"), None)?; + pool.submit_task( + None, + |_| { + println!("2"); + Some(2) + }, + None, + ) + .map(|_| ()) +} diff --git a/core/tests/coroutine.rs b/core/tests/coroutine.rs new file mode 100644 index 00000000..c08693a6 --- /dev/null +++ b/core/tests/coroutine.rs @@ -0,0 +1,235 @@ +use corosensei::stack::{DefaultStack, Stack}; +use open_coroutine_core::co; +use open_coroutine_core::common::constants::CoroutineState; +use open_coroutine_core::coroutine::suspender::Suspender; +use open_coroutine_core::coroutine::Coroutine; + +#[test] +fn coroutine_basic() -> std::io::Result<()> { + let mut coroutine = co!(|suspender, input| { + assert_eq!(1, input); + assert_eq!(3, suspender.suspend_with(2)); + 4 + })?; + assert_eq!(CoroutineState::Suspend(2, 0), coroutine.resume_with(1)?); + assert_eq!(CoroutineState::Complete(4), coroutine.resume_with(3)?); + Ok(()) +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn coroutine_panic() -> std::io::Result<()> { + let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { + panic!("test panic, just ignore it"); + })?; + let result = coroutine.resume()?; + let error = match result { + CoroutineState::Error(_) => true, + _ => false, + }; + assert!(error); + Ok(()) +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn coroutine_backtrace() -> std::io::Result<()> { + let mut coroutine = co!(|suspender, input| { + assert_eq!(1, input); + println!("{:?}", backtrace::Backtrace::new()); + assert_eq!(3, suspender.suspend_with(2)); + println!("{:?}", backtrace::Backtrace::new()); + 4 + })?; + assert_eq!(CoroutineState::Suspend(2, 0), coroutine.resume_with(1)?); + assert_eq!(CoroutineState::Complete(4), coroutine.resume_with(3)?); + Ok(()) +} + +#[test] +fn coroutine_delay() -> std::io::Result<()> { + let mut coroutine = co!(|s, ()| { + let current = Coroutine::<(), (), ()>::current().unwrap(); + assert_eq!(CoroutineState::Running, current.state()); + s.delay(std::time::Duration::MAX); + unreachable!(); + })?; + assert_eq!(CoroutineState::Ready, coroutine.state()); + assert_eq!(CoroutineState::Suspend((), u64::MAX), coroutine.resume()?); + assert_eq!(CoroutineState::Suspend((), u64::MAX), coroutine.state()); + assert_eq!( + format!( + "{} unexpected {}->{:?}", + coroutine.name(), + CoroutineState::<(), ()>::Suspend((), u64::MAX), + CoroutineState::<(), ()>::Running + ), + coroutine.resume().unwrap_err().to_string() + ); + assert_eq!(CoroutineState::Suspend((), u64::MAX), coroutine.state()); + Ok(()) +} + +#[test] +fn coroutine_stack_growth() -> std::io::Result<()> { + let mut coroutine = co!(|_: &Suspender<(), ()>, ()| { + fn recurse(i: u32, p: &mut [u8; 10240]) { + Coroutine::<(), (), ()>::maybe_grow(|| { + // Ensure the stack allocation isn't optimized away. + unsafe { std::ptr::read_volatile(&p) }; + if i > 0 { + recurse(i - 1, &mut [0; 10240]); + } + }) + .expect("allocate stack failed") + } + + let stack = DefaultStack::new(open_coroutine_core::common::constants::DEFAULT_STACK_SIZE) + .expect("allocate stack failed"); + let max_remaining = stack.base().get() - stack.limit().get(); + // Use ~500KB of stack. + recurse(50, &mut [0; 10240]); + let remaining_stack = unsafe { + Coroutine::<(), (), ()>::current() + .unwrap() + .remaining_stack() + }; + assert!( + remaining_stack < max_remaining, + "remaining stack {remaining_stack} when max {max_remaining}" + ); + // Use ~500KB of stack. + recurse(50, &mut [0; 10240]); + let remaining_stack = unsafe { + Coroutine::<(), (), ()>::current() + .unwrap() + .remaining_stack() + }; + assert!( + remaining_stack < max_remaining, + "remaining stack {remaining_stack} when max {max_remaining}" + ); + })?; + assert_eq!(coroutine.resume()?, CoroutineState::Complete(())); + Ok(()) +} + +#[test] +fn coroutine_trap() -> std::io::Result<()> { + let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { + println!("Before trap"); + unsafe { std::ptr::write_volatile(1 as *mut u8, 0) }; + println!("After trap"); + })?; + let result = coroutine.resume()?; + let error = match result { + CoroutineState::Error(_) => true, + _ => false, + }; + assert!(error); + Ok(()) +} + +#[cfg(not(debug_assertions))] +#[test] +fn coroutine_invalid_memory_reference() -> std::io::Result<()> { + let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { + println!("Before invalid memory reference"); + // 没有加--release运行,会收到SIGABRT信号,不好处理,直接禁用测试 + unsafe { + let co = &*((1usize as *mut std::ffi::c_void).cast::>()); + println!("{}", co.state()); + } + println!("After invalid memory reference"); + })?; + let result = coroutine.resume(); + assert!(result.is_ok()); + println!("{:?}", result); + let error = match result.unwrap() { + CoroutineState::Error(_) => true, + _ => false, + }; + assert!(error); + Ok(()) +} + +#[cfg(all(unix, feature = "preemptive"))] +#[test] +fn coroutine_preemptive() -> std::io::Result<()> { + let pair = std::sync::Arc::new((std::sync::Mutex::new(true), std::sync::Condvar::new())); + let pair2 = pair.clone(); + _ = std::thread::Builder::new() + .name("preemptive".to_string()) + .spawn(move || { + let mut coroutine: Coroutine<(), (), ()> = co!(|_, ()| { loop {} })?; + assert_eq!(CoroutineState::Suspend((), 0), coroutine.resume()?); + assert_eq!(CoroutineState::Suspend((), 0), coroutine.state()); + // should execute to here + let (lock, cvar) = &*pair2; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + Ok::<(), std::io::Error>(()) + }); + // wait for the thread to start up + let (lock, cvar) = &*pair; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + std::time::Duration::from_millis(3000), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "The monitor should send signals to coroutines in running state", + )) + } else { + Ok(()) + } +} + +#[cfg(all(unix, feature = "preemptive"))] +#[test] +fn coroutine_syscall_not_preemptive() -> std::io::Result<()> { + use open_coroutine_core::common::constants::{Syscall, SyscallState}; + + let pair = std::sync::Arc::new((std::sync::Mutex::new(true), std::sync::Condvar::new())); + let pair2 = pair.clone(); + _ = std::thread::Builder::new() + .name("syscall_not_preemptive".to_string()) + .spawn(move || { + let mut coroutine: Coroutine<(), (), ()> = co!(|_, ()| { + Coroutine::<(), (), ()>::current() + .unwrap() + .syscall((), Syscall::sleep, SyscallState::Executing) + .unwrap(); + loop {} + })?; + _ = coroutine.resume()?; + // should never execute to here + let (lock, cvar) = &*pair2; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + Ok::<(), std::io::Error>(()) + }); + // wait for the thread to start up + let (lock, cvar) = &*pair; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + std::time::Duration::from_millis(1000), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "The monitor should not send signals to coroutines in syscall state", + )) + } +} diff --git a/core/tests/scheduler.rs b/core/tests/scheduler.rs new file mode 100644 index 00000000..89677cd2 --- /dev/null +++ b/core/tests/scheduler.rs @@ -0,0 +1,123 @@ +use open_coroutine_core::scheduler::Scheduler; +use std::time::Duration; + +#[test] +fn scheduler_basic() -> std::io::Result<()> { + let mut scheduler = Scheduler::default(); + _ = scheduler.submit_co( + |_, _| { + println!("1"); + None + }, + None, + )?; + _ = scheduler.submit_co( + |_, _| { + println!("2"); + None + }, + None, + )?; + scheduler.try_schedule() +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn scheduler_backtrace() -> std::io::Result<()> { + let mut scheduler = Scheduler::default(); + _ = scheduler.submit_co(|_, _| None, None)?; + _ = scheduler.submit_co( + |_, _| { + println!("{:?}", backtrace::Backtrace::new()); + None + }, + None, + )?; + scheduler.try_schedule() +} + +#[test] +fn scheduler_suspend() -> std::io::Result<()> { + let mut scheduler = Scheduler::default(); + _ = scheduler.submit_co( + |suspender, _| { + println!("[coroutine1] suspend"); + suspender.suspend(); + println!("[coroutine1] back"); + None + }, + None, + )?; + _ = scheduler.submit_co( + |suspender, _| { + println!("[coroutine2] suspend"); + suspender.suspend(); + println!("[coroutine2] back"); + None + }, + None, + )?; + scheduler.try_schedule() +} + +#[test] +fn scheduler_delay() -> std::io::Result<()> { + let mut scheduler = Scheduler::default(); + _ = scheduler.submit_co( + |suspender, _| { + println!("[coroutine] delay"); + suspender.delay(Duration::from_millis(100)); + println!("[coroutine] back"); + None + }, + None, + )?; + scheduler.try_schedule()?; + std::thread::sleep(Duration::from_millis(100)); + scheduler.try_schedule() +} + +#[cfg(not(all(unix, feature = "preemptive")))] +#[test] +fn scheduler_listener() -> std::io::Result<()> { + use open_coroutine_core::coroutine::listener::Listener; + use open_coroutine_core::coroutine::local::CoroutineLocal; + use open_coroutine_core::scheduler::SchedulableCoroutineState; + + #[derive(Debug, Default)] + struct TestListener {} + impl Listener<(), Option> for TestListener { + fn on_create(&self, local: &CoroutineLocal, _: usize) { + println!("{:?}", local); + } + + fn on_state_changed( + &self, + local: &CoroutineLocal, + old_state: SchedulableCoroutineState, + new_state: SchedulableCoroutineState, + ) { + println!("{} {}->{}", local, old_state, new_state); + } + + fn on_complete(&self, _: &CoroutineLocal, _: SchedulableCoroutineState, _: Option) { + panic!("test on_complete panic, just ignore it"); + } + + fn on_error(&self, _: &CoroutineLocal, _: SchedulableCoroutineState, _: &str) { + panic!("test on_error panic, just ignore it"); + } + } + + let mut scheduler = Scheduler::default(); + scheduler.add_listener(TestListener::default()); + scheduler.submit_co(|_, _| panic!("test panic, just ignore it"), None)?; + scheduler.submit_co( + |_, _| { + println!("2"); + None + }, + None, + )?; + scheduler.try_schedule() +} diff --git a/deny.toml b/deny.toml index afa8d5bb..bb7965af 100644 --- a/deny.toml +++ b/deny.toml @@ -2,10 +2,9 @@ allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", - "Zlib", - "BSD-3-Clause", "Unlicense", "Unicode-DFS-2016", + "MIT" ] confidence-threshold = 0.95 private = {ignore = true} diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index ef529893..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "open-coroutine-examples" -version = "0.1.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The examples for open-coroutine" -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/examples" -keywords = ["open-coroutine", "example"] -categories = ["os", "concurrency", "asynchronous"] -license = "LGPL-3.0 OR Apache-2.0" -readme = "../README.md" - -[dependencies] -open-coroutine = { version = "0.5.0", path = "../open-coroutine", features = [ - "preemptive-schedule", "logs"], default-features = false } - -[features] -# Provide io_uring abstraction and implementation. -# This feature only works in linux. -io_uring = ["open-coroutine/io_uring"] diff --git a/examples/examples/sleep_co.rs b/examples/examples/sleep_co.rs deleted file mode 100644 index 3e7de9ec..00000000 --- a/examples/examples/sleep_co.rs +++ /dev/null @@ -1,7 +0,0 @@ -use open_coroutine_examples::sleep_test_co; - -#[open_coroutine::main(event_loop_size = 2, max_size = 2, keep_alive_time = 0)] -fn main() { - sleep_test_co(1); - sleep_test_co(1000); -} diff --git a/examples/examples/sleep_not_co.rs b/examples/examples/sleep_not_co.rs deleted file mode 100644 index 74feeb06..00000000 --- a/examples/examples/sleep_not_co.rs +++ /dev/null @@ -1,7 +0,0 @@ -use open_coroutine_examples::sleep_test; - -#[open_coroutine::main(event_loop_size = 2, max_size = 2, keep_alive_time = 0)] -fn main() { - sleep_test(1); - sleep_test(1000); -} diff --git a/examples/examples/socket_co.rs b/examples/examples/socket_co.rs deleted file mode 100644 index 404f8323..00000000 --- a/examples/examples/socket_co.rs +++ /dev/null @@ -1,38 +0,0 @@ -use open_coroutine_examples::{crate_co_client, crate_co_server}; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -#[open_coroutine::main(event_loop_size = 2, max_size = 2)] -fn main() -> std::io::Result<()> { - let port = 8999; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); - let server_finished = Arc::clone(&server_finished_pair); - _ = std::thread::Builder::new() - .name("crate_co_server".to_string()) - .spawn(move || crate_co_server(port, clone, server_finished_pair)) - .expect("failed to spawn thread"); - _ = std::thread::Builder::new() - .name("crate_co_client".to_string()) - .spawn(move || crate_co_client(port, server_started)) - .expect("failed to spawn thread"); - - let (lock, cvar) = &*server_finished; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_secs(30), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "The coroutine server and coroutine client did not completed within the specified time", - )) - } else { - Ok(()) - } -} diff --git a/examples/examples/socket_co_client.rs b/examples/examples/socket_co_client.rs deleted file mode 100644 index e91a4488..00000000 --- a/examples/examples/socket_co_client.rs +++ /dev/null @@ -1,38 +0,0 @@ -use open_coroutine_examples::{crate_co_client, crate_server}; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -#[open_coroutine::main(event_loop_size = 2, max_size = 1)] -fn main() -> std::io::Result<()> { - let port = 8899; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); - let server_finished = Arc::clone(&server_finished_pair); - _ = std::thread::Builder::new() - .name("crate_server".to_string()) - .spawn(move || crate_server(port, clone, server_finished_pair)) - .expect("failed to spawn thread"); - _ = std::thread::Builder::new() - .name("crate_co_client".to_string()) - .spawn(move || crate_co_client(port, server_started)) - .expect("failed to spawn thread"); - - let (lock, cvar) = &*server_finished; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_secs(30), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "The coroutine client did not completed within the specified time", - )) - } else { - Ok(()) - } -} diff --git a/examples/examples/socket_co_server.rs b/examples/examples/socket_co_server.rs deleted file mode 100644 index 3628f0e6..00000000 --- a/examples/examples/socket_co_server.rs +++ /dev/null @@ -1,38 +0,0 @@ -use open_coroutine_examples::{crate_client, crate_co_server}; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -#[open_coroutine::main(event_loop_size = 2, max_size = 1)] -fn main() -> std::io::Result<()> { - let port = 8889; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); - let server_finished = Arc::clone(&server_finished_pair); - _ = std::thread::Builder::new() - .name("crate_co_server".to_string()) - .spawn(move || crate_co_server(port, clone, server_finished_pair)) - .expect("failed to spawn thread"); - _ = std::thread::Builder::new() - .name("crate_client".to_string()) - .spawn(move || crate_client(port, server_started)) - .expect("failed to spawn thread"); - - let (lock, cvar) = &*server_finished; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_secs(30), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "The coroutine service did not completed within the specified time", - )) - } else { - Ok(()) - } -} diff --git a/examples/examples/socket_not_co.rs b/examples/examples/socket_not_co.rs deleted file mode 100644 index 7ceef408..00000000 --- a/examples/examples/socket_not_co.rs +++ /dev/null @@ -1,38 +0,0 @@ -use open_coroutine_examples::{crate_client, crate_server}; -use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -#[open_coroutine::main(event_loop_size = 2, max_size = 1)] -fn main() -> std::io::Result<()> { - let port = 8888; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); - let server_finished = Arc::clone(&server_finished_pair); - _ = std::thread::Builder::new() - .name("crate_server".to_string()) - .spawn(move || crate_server(port, clone, server_finished_pair)) - .expect("failed to spawn thread"); - _ = std::thread::Builder::new() - .name("crate_client".to_string()) - .spawn(move || crate_client(port, server_started)) - .expect("failed to spawn thread"); - - let (lock, cvar) = &*server_finished; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_secs(30), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "The service did not completed within the specified time", - )) - } else { - Ok(()) - } -} diff --git a/examples/src/lib.rs b/examples/src/lib.rs deleted file mode 100644 index 604c5ac0..00000000 --- a/examples/src/lib.rs +++ /dev/null @@ -1,370 +0,0 @@ -use open_coroutine::task; -use std::io::{BufRead, BufReader, ErrorKind, IoSlice, IoSliceMut, Read, Write}; -use std::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -fn crate_task(input: i32) { - _ = task!( - |_, param| { - println!("[coroutine{}] launched", param); - }, - input, - ); -} - -pub fn start_server(addr: A, server_finished: Arc<(Mutex, Condvar)>) { - let listener = TcpListener::bind(addr).expect("start server failed"); - for stream in listener.incoming() { - let mut socket = stream.expect("accept new connection failed"); - let mut buffer1 = [0; 256]; - for _ in 0..3 { - assert_eq!(12, socket.read(&mut buffer1).expect("recv failed")); - println!("Server Received: {}", String::from_utf8_lossy(&buffer1)); - assert_eq!(256, socket.write(&buffer1).expect("send failed")); - println!("Server Send"); - } - let mut buffer2 = [0; 256]; - for _ in 0..3 { - let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; - assert_eq!( - 26, - socket.read_vectored(&mut buffers).expect("readv failed") - ); - println!( - "Server Received Multiple: {}{}", - String::from_utf8_lossy(&buffer1), - String::from_utf8_lossy(&buffer2) - ); - let responses = [IoSlice::new(&buffer1), IoSlice::new(&buffer2)]; - assert_eq!( - 512, - socket.write_vectored(&responses).expect("writev failed") - ); - println!("Server Send Multiple"); - } - println!("Server Shutdown Write"); - if socket.shutdown(Shutdown::Write).is_ok() { - println!("Server Closed Connection"); - let (lock, cvar) = &*server_finished; - let mut pending = lock.lock().unwrap(); - *pending = false; - cvar.notify_one(); - println!("Server Closed"); - return; - } - } -} - -pub fn start_client(addr: A) { - let mut stream = connect_timeout(addr, Duration::from_secs(3)).expect("connect failed"); - let mut buffer1 = [0; 256]; - for i in 0..3 { - assert_eq!( - 12, - stream - .write(format!("RequestPart{i}").as_ref()) - .expect("send failed") - ); - println!("Client Send"); - assert_eq!(256, stream.read(&mut buffer1).expect("recv failed")); - println!("Client Received: {}", String::from_utf8_lossy(&buffer1)); - } - let mut buffer2 = [0; 256]; - for i in 0..3 { - let request1 = format!("RequestPart{i}1"); - let request2 = format!("RequestPart{i}2"); - let requests = [ - IoSlice::new(request1.as_ref()), - IoSlice::new(request2.as_ref()), - ]; - assert_eq!(26, stream.write_vectored(&requests).expect("writev failed")); - println!("Client Send Multiple"); - let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; - assert_eq!( - 512, - stream.read_vectored(&mut buffers).expect("readv failed") - ); - println!( - "Client Received Multiple: {}{}", - String::from_utf8_lossy(&buffer1), - String::from_utf8_lossy(&buffer2) - ); - } - println!("Client Shutdown Write"); - stream.shutdown(Shutdown::Write).expect("shutdown failed"); - println!("Client Closed"); -} - -fn connect_timeout(addr: A, timeout: Duration) -> std::io::Result { - let mut last_err = None; - for addr in addr.to_socket_addrs()? { - match TcpStream::connect_timeout(&addr, timeout) { - Ok(l) => return Ok(l), - Err(e) => last_err = Some(e), - } - } - Err(last_err.unwrap_or_else(|| { - std::io::Error::new( - ErrorKind::InvalidInput, - "could not resolve to any addresses", - ) - })) -} - -pub fn crate_server( - port: u16, - server_started: Arc, - server_finished: Arc<(Mutex, Condvar)>, -) { - //invoke by libc::listen - crate_task(1); - let mut data: [u8; 512] = unsafe { std::mem::zeroed() }; - data[511] = b'\n'; - let listener = TcpListener::bind((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)) - .unwrap_or_else(|_| panic!("bind to 127.0.0.1:{port} failed !")); - server_started.store(true, Ordering::Release); - //invoke by libc::accept - crate_task(2); - if let Some(stream) = listener.incoming().next() { - let mut stream = stream.expect("accept new connection failed !"); - let mut buffer: [u8; 512] = [0; 512]; - loop { - //invoke by libc::recv - crate_task(6); - //从流里面读内容,读到buffer中 - let bytes_read = stream.read(&mut buffer).expect("server read failed !"); - if bytes_read == 0 { - println!("server close a connection"); - continue; - } - print!("Server Received: {}", String::from_utf8_lossy(&buffer[..])); - if bytes_read == 1 && buffer[0] == b'e' { - //如果读到的为空,说明已经结束了 - let (lock, cvar) = &*server_finished; - let mut pending = lock.lock().unwrap(); - *pending = false; - cvar.notify_one(); - println!("server closed"); - crate_task(8); - return; - } - assert_eq!(512, bytes_read); - assert_eq!(data, buffer); - //invoke by libc::send - crate_task(7); - //回写 - assert_eq!( - bytes_read, - stream - .write(&buffer[..bytes_read]) - .expect("server write failed !") - ); - print!( - "Server Send: {}", - String::from_utf8_lossy(&buffer[..bytes_read]) - ); - } - } -} - -pub fn crate_client(port: u16, server_started: Arc) { - //等服务端起来 - while !server_started.load(Ordering::Acquire) {} - //invoke by libc::connect - crate_task(3); - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); - let mut stream = TcpStream::connect_timeout(&socket, Duration::from_secs(3)) - .unwrap_or_else(|_| panic!("connect to 127.0.0.1:{port} failed !")); - let mut data: [u8; 512] = unsafe { std::mem::zeroed() }; - data[511] = b'\n'; - let mut buffer: Vec = Vec::with_capacity(512); - for _ in 0..3 { - //invoke by libc::send - crate_task(4); - //写入stream流,如果写入失败,提示"写入失败" - assert_eq!(512, stream.write(&data).expect("Failed to write!")); - print!("Client Send: {}", String::from_utf8_lossy(&data[..])); - - //invoke by libc::recv - crate_task(5); - let mut reader = BufReader::new(&stream); - //一直读到换行为止(b'\n'中的b表示字节),读到buffer里面 - assert_eq!( - 512, - reader - .read_until(b'\n', &mut buffer) - .expect("Failed to read into buffer") - ); - print!("Client Received: {}", String::from_utf8_lossy(&buffer[..])); - assert_eq!(&data, &buffer as &[u8]); - buffer.clear(); - } - //发送终止符 - assert_eq!(1, stream.write(&[b'e']).expect("Failed to write!")); - println!("client closed"); - crate_task(8); -} - -pub fn crate_co_server( - port: u16, - server_started: Arc, - server_finished: Arc<(Mutex, Condvar)>, -) { - //invoke by libc::listen - crate_task(11); - let mut data: [u8; 512] = unsafe { std::mem::zeroed() }; - data[511] = b'\n'; - let listener = TcpListener::bind((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)) - .unwrap_or_else(|_| panic!("bind to 127.0.0.1:{port} failed !")); - server_started.store(true, Ordering::Release); - //invoke by libc::accept - crate_task(12); - for stream in listener.incoming() { - _ = task!( - |_, input| { - let mut stream = input.expect("accept new connection failed !"); - let mut buffer: [u8; 512] = [0; 512]; - loop { - //invoke by libc::recv - crate_task(16); - //从流里面读内容,读到buffer中 - let bytes_read = stream - .read(&mut buffer) - .expect("coroutine server read failed !"); - if bytes_read == 0 { - println!("coroutine server close a connection"); - return None; - } - print!( - "Coroutine Server Received: {}", - String::from_utf8_lossy(&buffer[..]) - ); - if bytes_read == 1 && buffer[0] == b'e' { - //如果读到的为空,说明已经结束了 - let (lock, cvar) = &*server_finished; - let mut pending = lock.lock().unwrap(); - *pending = false; - cvar.notify_one(); - println!("coroutine server closed"); - crate_task(18); - return Some(Box::leak(Box::new(stream))); - } - assert_eq!(512, bytes_read); - assert_eq!(data, buffer); - //invoke by libc::send - crate_task(17); - //回写 - assert_eq!( - bytes_read, - stream - .write(&buffer[..bytes_read]) - .expect("coroutine server write failed !") - ); - print!( - "Coroutine Server Send: {}", - String::from_utf8_lossy(&buffer[..bytes_read]) - ); - } - }, - stream, - ); - } -} - -pub fn crate_co_client(port: u16, server_started: Arc) { - //等服务端起来 - while !server_started.load(Ordering::Acquire) {} - _ = task!( - |_, input| { - //invoke by libc::connect - crate_task(13); - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), input); - let mut stream = TcpStream::connect_timeout(&socket, Duration::from_secs(3)) - .unwrap_or_else(|_| panic!("connect to 127.0.0.1:{input} failed !")); - let mut data: [u8; 512] = unsafe { std::mem::zeroed() }; - data[511] = b'\n'; - let mut buffer: Vec = Vec::with_capacity(512); - for _ in 0..3 { - //invoke by libc::send - crate_task(14); - //写入stream流,如果写入失败,提示"写入失败" - assert_eq!(512, stream.write(&data).expect("Failed to write!")); - print!( - "Coroutine Client Send: {}", - String::from_utf8_lossy(&data[..]) - ); - - //invoke by libc::recv - crate_task(15); - let mut reader = BufReader::new(&stream); - //一直读到换行为止(b'\n'中的b表示字节),读到buffer里面 - assert_eq!( - 512, - reader - .read_until(b'\n', &mut buffer) - .expect("Failed to read into buffer") - ); - print!( - "Coroutine Client Received: {}", - String::from_utf8_lossy(&buffer[..]) - ); - assert_eq!(&data, &buffer as &[u8]); - buffer.clear(); - } - //发送终止符 - assert_eq!(1, stream.write(&[b'e']).expect("Failed to write!")); - println!("coroutine client closed"); - crate_task(18); - }, - port, - ); -} - -fn now() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("1970-01-01 00:00:00 UTC was {} seconds ago!") - .as_nanos() as u64 -} - -pub fn sleep_test(millis: u64) { - _ = task!( - move |_, _| { - println!("[coroutine1] {millis} launched"); - }, - (), - ); - _ = task!( - move |_, _| { - println!("[coroutine2] {millis} launched"); - }, - (), - ); - let start = now(); - std::thread::sleep(Duration::from_millis(millis)); - let end = now(); - assert!(end - start >= millis, "Time consumption less than expected"); -} - -pub fn sleep_test_co(millis: u64) { - _ = task!( - move |_, _| { - let start = now(); - std::thread::sleep(Duration::from_millis(millis)); - let end = now(); - assert!(end - start >= millis, "Time consumption less than expected"); - println!("[coroutine1] {millis} launched"); - }, - (), - ); - _ = task!( - move |_, _| { - std::thread::sleep(Duration::from_millis(500)); - println!("[coroutine2] {millis} launched"); - }, - (), - ); - std::thread::sleep(Duration::from_millis(millis + 500)); -} diff --git a/hook/Cargo.toml b/hook/Cargo.toml new file mode 100644 index 00000000..89eaae6b --- /dev/null +++ b/hook/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "open-coroutine-hook" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "The syscall hook for open-coroutine" +repository.workspace = true +keywords = ["open-coroutine", "hook", "syscall"] +categories = ["os", "concurrency", "asynchronous"] +license.workspace = true +readme.workspace = true + +[dependencies] +once_cell.workspace = true +open-coroutine-core.workspace = true + +[target.'cfg(unix)'.dependencies] +libc.workspace = true + +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", + "Win32_System_Threading", + "Win32_Security", + "Win32_System_LibraryLoader", + "Win32_System_SystemServices" +] } +minhook.workspace = true + +[features] +# Print some help log. +# Enable for default. +log = ["open-coroutine-core/log"] + +# Provide preemptive scheduling implementation. +# Enable for default. +preemptive = ["open-coroutine-core/preemptive"] + +# Provide net API abstraction and implementation. +net = ["open-coroutine-core/net"] + +# Provide io_uring adaptation, this feature only works in linux. +io_uring = ["open-coroutine-core/io_uring"] + +# Provide syscall implementation. +syscall = ["open-coroutine-core/syscall"] + +default = ["open-coroutine-core/default"] + +[lib] +crate-type = ["cdylib"] diff --git a/hook/src/lib.rs b/hook/src/lib.rs new file mode 100644 index 00000000..51b153de --- /dev/null +++ b/hook/src/lib.rs @@ -0,0 +1,154 @@ +#![deny( + // The following are allowed by default lints according to + // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html + anonymous_parameters, + bare_trait_objects, + // elided_lifetimes_in_paths, // allow anonymous lifetime + missing_copy_implementations, + missing_debug_implementations, + missing_docs, // TODO: add documents + single_use_lifetimes, // TODO: fix lifetime names only used once + trivial_casts, // TODO: remove trivial casts in code + trivial_numeric_casts, + // unreachable_pub, allow clippy::redundant_pub_crate lint instead + // unsafe_code, + unstable_features, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + unused_results, + variant_size_differences, + + warnings, // treat all wanings as errors + + clippy::all, + // clippy::restriction, + clippy::pedantic, + // clippy::nursery, // It's still under development + clippy::cargo, + unreachable_pub, +)] +#![allow( + // Some explicitly allowed Clippy lints, must have clear reason to allow + clippy::blanket_clippy_restriction_lints, // allow clippy::restriction + clippy::implicit_return, // actually omitting the return keyword is idiomatic Rust code + clippy::module_name_repetitions, // repeation of module name in a struct name is not big deal + clippy::multiple_crate_versions, // multi-version dependency crates is not able to fix + clippy::missing_errors_doc, // TODO: add error docs + clippy::missing_panics_doc, // TODO: add panic docs + clippy::panic_in_result_fn, + clippy::shadow_same, // Not too much bad + clippy::shadow_reuse, // Not too much bad + clippy::exhaustive_enums, + clippy::exhaustive_structs, + clippy::indexing_slicing, + clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix + clippy::single_char_lifetime_names, // TODO: change lifetime names +)] +//! see `https://github.com/acl-dev/open-coroutine` + +use once_cell::sync::OnceCell; +use open_coroutine_core::co_pool::task::UserTaskFunc; +use open_coroutine_core::net::config::Config; +use open_coroutine_core::net::join::JoinHandle; +use open_coroutine_core::net::{EventLoops, UserFunc}; +use open_coroutine_core::scheduler::SchedulableCoroutine; +use std::ffi::{c_int, c_longlong, c_uint}; +use std::time::Duration; + +static HOOK: OnceCell = OnceCell::new(); + +pub(crate) fn hook() -> bool { + HOOK.get().map_or_else(|| false, |v| *v) +} + +#[allow( + dead_code, + missing_docs, + clippy::similar_names, + clippy::not_unsafe_ptr_arg_deref, + clippy::many_single_char_names, + clippy::cast_sign_loss, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::unnecessary_cast, + trivial_numeric_casts +)] +pub mod syscall; + +/// Start the framework. +#[no_mangle] +pub extern "C" fn open_coroutine_init(config: Config) -> c_int { + EventLoops::init(&config); + _ = HOOK.get_or_init(|| config.hook()); + 0 +} + +/// Stop the framework. +#[no_mangle] +pub extern "C" fn open_coroutine_stop(secs: c_uint) -> c_int { + if EventLoops::stop(Duration::from_secs(u64::from(secs))).is_ok() { + return 0; + } + -1 +} + +///创建任务 +#[no_mangle] +pub extern "C" fn task_crate(f: UserTaskFunc, param: usize) -> JoinHandle { + EventLoops::submit_task(None, move |p| Some(f(p.unwrap_or(0))), Some(param)) +} + +///等待任务完成 +#[no_mangle] +pub extern "C" fn task_join(handle: &JoinHandle) -> c_longlong { + match handle.join() { + Ok(ptr) => match ptr { + Ok(ptr) => match ptr { + Some(ptr) => c_longlong::try_from(ptr).expect("overflow"), + None => 0, + }, + Err(_) => -1, + }, + Err(_) => -1, + } +} + +///等待任务完成 +#[no_mangle] +pub extern "C" fn task_timeout_join(handle: &JoinHandle, ns_time: u64) -> c_longlong { + match handle.timeout_join(Duration::from_nanos(ns_time)) { + Ok(ptr) => match ptr { + Ok(ptr) => match ptr { + Some(ptr) => c_longlong::try_from(ptr).expect("overflow"), + None => 0, + }, + Err(_) => -1, + }, + Err(_) => -1, + } +} + +///如果当前协程栈不够,切换到新栈上执行 +#[no_mangle] +pub extern "C" fn maybe_grow_stack( + red_zone: usize, + stack_size: usize, + f: UserFunc, + param: usize, +) -> c_longlong { + let red_zone = if red_zone > 0 { + red_zone + } else { + open_coroutine_core::common::default_red_zone() + }; + let stack_size = if stack_size > 0 { + stack_size + } else { + open_coroutine_core::common::constants::DEFAULT_STACK_SIZE + }; + if let Ok(r) = SchedulableCoroutine::maybe_grow_with(red_zone, stack_size, || f(param)) { + return c_longlong::try_from(r).expect("overflow"); + } + -1 +} diff --git a/hook/src/syscall/mod.rs b/hook/src/syscall/mod.rs new file mode 100644 index 00000000..b72b76d3 --- /dev/null +++ b/hook/src/syscall/mod.rs @@ -0,0 +1,6 @@ +#[cfg(unix)] +pub mod unix; + +#[allow(non_snake_case)] +#[cfg(windows)] +pub mod windows; diff --git a/hook/src/syscall/unix.rs b/hook/src/syscall/unix.rs new file mode 100644 index 00000000..ee263d3f --- /dev/null +++ b/hook/src/syscall/unix.rs @@ -0,0 +1,69 @@ +use libc::{ + fd_set, iovec, msghdr, off_t, pthread_cond_t, pthread_mutex_t, size_t, sockaddr, socklen_t, + ssize_t, timespec, timeval, +}; +use std::ffi::{c_int, c_uint, c_void}; + +// check https://www.rustwiki.org.cn/en/reference/introduction.html for help information +#[allow(unused_macros)] +macro_rules! impl_hook { + ( $field_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + #[no_mangle] + pub extern "C" fn $syscall( + $($arg: $arg_type),* + ) -> $result { + static $field_name: once_cell::sync::Lazy< + extern "C" fn($($arg_type, )*) -> $result, + > = once_cell::sync::Lazy::new(|| unsafe { + let syscall: &str = open_coroutine_core::common::constants::Syscall::$syscall.into(); + let symbol = std::ffi::CString::new(String::from(syscall)) + .unwrap_or_else(|_| panic!("can not transfer \"{syscall}\" to CString")); + let ptr = libc::dlsym(libc::RTLD_NEXT, symbol.as_ptr()); + assert!(!ptr.is_null(), "system call \"{syscall}\" not found !"); + std::mem::transmute(ptr) + }); + let fn_ptr = once_cell::sync::Lazy::force(&$field_name); + if $crate::hook() { + return open_coroutine_core::syscall::$syscall(Some(fn_ptr), $($arg, )*); + } + (fn_ptr)($($arg),*) + } + } +} + +impl_hook!(SLEEP, sleep(secs: c_uint) -> c_uint); +impl_hook!(USLEEP, usleep(microseconds: c_uint) -> c_int); +impl_hook!(NANOSLEEP, nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int); +// NOTE: unhook poll due to mio's poller +// impl_hook!(POLL, poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int); +impl_hook!(SELECT, select(nfds: c_int, readfds: *mut fd_set, writefds: *mut fd_set, errorfds: *mut fd_set, timeout: *mut timeval) -> c_int); +impl_hook!(SOCKET, socket(domain: c_int, type_: c_int, protocol: c_int) -> c_int); +impl_hook!(CONNECT, connect(fd: c_int, address: *const sockaddr, len: socklen_t) -> c_int); +impl_hook!(LISTEN, listen(fd: c_int, backlog: c_int) -> c_int); +impl_hook!(ACCEPT, accept(fd: c_int, address: *mut sockaddr, address_len: *mut socklen_t) -> c_int); +#[cfg(any( + target_os = "linux", + target_os = "l4re", + target_os = "android", + target_os = "emscripten" +))] +impl_hook!(ACCEPT4, accept4(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t, flg: c_int) -> c_int); +impl_hook!(SHUTDOWN, shutdown(fd: c_int, how: c_int) -> c_int); +impl_hook!(RECV, recv(fd: c_int, buf: *mut c_void, len: size_t, flags: c_int) -> ssize_t); +impl_hook!(RECVFROM, recvfrom(fd: c_int, buf: *mut c_void, len: size_t, flags: c_int, addr: *mut sockaddr, addrlen: *mut socklen_t) -> ssize_t); +impl_hook!(PREAD, pread(fd: c_int, buf: *mut c_void, count: size_t, offset: off_t) -> ssize_t); +impl_hook!(READV, readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_hook!(PREADV, preadv(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_hook!(RECVMSG, recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t); +impl_hook!(SEND, send(fd: c_int, buf: *const c_void, len: size_t, flags: c_int) -> ssize_t); +impl_hook!(SENDTO, sendto(fd: c_int, buf: *const c_void, len: size_t, flags: c_int, addr: *const sockaddr, addrlen: socklen_t) -> ssize_t); +impl_hook!(PWRITE, pwrite(fd: c_int, buf: *const c_void, count: size_t, offset: off_t) -> ssize_t); +impl_hook!(WRITEV, writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t); +impl_hook!(PWRITEV, pwritev(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t); +impl_hook!(SENDMSG, sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t); +impl_hook!(PTHREAD_COND_TIMEDWAIT, pthread_cond_timedwait(cond: *mut pthread_cond_t, lock: *mut pthread_mutex_t, abstime: *const timespec) -> c_int); +// NOTE: unhook pthread_mutex_lock due to stack overflow +// impl_hook!(PTHREAD_MUTEX_LOCK, pthread_mutex_lock(lock: *mut pthread_mutex_t) -> c_int); +impl_hook!(PTHREAD_MUTEX_TRYLOCK, pthread_mutex_trylock(lock: *mut pthread_mutex_t) -> c_int); +// NOTE: unhook pthread_mutex_unlock due to stack overflow +// impl_hook!(PTHREAD_MUTEX_UNLOCK, pthread_mutex_unlock(lock: *mut pthread_mutex_t) -> c_int); diff --git a/hook/src/syscall/windows.rs b/hook/src/syscall/windows.rs new file mode 100644 index 00000000..e4cbc8af --- /dev/null +++ b/hook/src/syscall/windows.rs @@ -0,0 +1,121 @@ +use std::ffi::{c_int, c_uint, c_void}; +use std::io::{Error, ErrorKind}; +use windows_sys::core::{PCSTR, PSTR}; +use windows_sys::Win32::Foundation::{BOOL, TRUE}; +use windows_sys::Win32::Networking::WinSock::{ + IPPROTO, LPWSAOVERLAPPED_COMPLETION_ROUTINE, SEND_RECV_FLAGS, SOCKADDR, SOCKET, + WINSOCK_SHUTDOWN_HOW, WINSOCK_SOCKET_TYPE, WSABUF, WSAPROTOCOL_INFOW, +}; +use windows_sys::Win32::System::SystemServices::{DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH}; +use windows_sys::Win32::System::IO::OVERLAPPED; + +// check https://www.rustwiki.org.cn/en/reference/introduction.html for help information +#[allow(unused_macros)] +macro_rules! impl_hook { + ( $module_name: expr, $field_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { + static $field_name: once_cell::sync::OnceCell $result> = + once_cell::sync::OnceCell::new(); + _ = $field_name.get_or_init(|| unsafe { + let syscall: &str = open_coroutine_core::common::constants::Syscall::$syscall.into(); + let ptr = minhook::MinHook::create_hook_api($module_name, syscall, $syscall as _) + .unwrap_or_else(|_| panic!("hook {syscall} failed !")); + assert!(!ptr.is_null(), "syscall \"{syscall}\" not found !"); + std::mem::transmute(ptr) + }); + #[allow(non_snake_case)] + extern "system" fn $syscall($($arg: $arg_type),*) -> $result { + let fn_ptr = $field_name.get().unwrap_or_else(|| { + panic!( + "hook {} failed !", + open_coroutine_core::common::constants::Syscall::$syscall + ) + }); + if $crate::hook() { + return open_coroutine_core::syscall::$syscall(Some(fn_ptr), $($arg),*); + } + (fn_ptr)($($arg),*) + } + } +} + +#[no_mangle] +#[allow(non_snake_case, clippy::missing_safety_doc)] +pub unsafe extern "system" fn DllMain( + _module: *mut c_void, + call_reason: u32, + _reserved: *mut c_void, +) -> BOOL { + // Preferably a thread should be created here instead, since as few + // operations as possible should be performed within `DllMain`. + if call_reason == DLL_PROCESS_ATTACH { + // Called when the DLL is attached to the process. + BOOL::from(attach().is_ok()) + } else if call_reason == DLL_PROCESS_DETACH { + // Called when the DLL is detached to the process. + BOOL::from(minhook::MinHook::disable_all_hooks().is_ok()) + } else { + TRUE + } +} + +unsafe fn attach() -> std::io::Result<()> { + impl_hook!("ws2_32.dll", ACCEPT, accept( + fd: SOCKET, + address: *mut SOCKADDR, + address_len: *mut c_int + ) -> SOCKET); + impl_hook!("ws2_32.dll", IOCTLSOCKET, ioctlsocket( + fd: SOCKET, + cmd: c_int, + argp: *mut c_uint + ) -> c_int); + impl_hook!("ws2_32.dll", LISTEN, listen(fd: SOCKET, backlog: c_int) -> c_int); + impl_hook!("ws2_32.dll", RECV, recv( + fd: SOCKET, + buf: PSTR, + len: c_int, + flags: SEND_RECV_FLAGS + ) -> c_int); + impl_hook!("ws2_32.dll", SEND, send( + fd: SOCKET, + buf: PCSTR, + len: c_int, + flags: SEND_RECV_FLAGS + ) -> c_int); + impl_hook!("ws2_32.dll", SHUTDOWN, shutdown(fd: SOCKET, how: WINSOCK_SHUTDOWN_HOW) -> c_int); + impl_hook!("kernel32.dll", SLEEP, Sleep(dw_milliseconds: u32) -> ()); + impl_hook!("ws2_32.dll", SOCKET, socket( + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO + ) -> SOCKET); + impl_hook!("ws2_32.dll", WSARECV, WSARecv( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + lpflags : *mut c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int); + impl_hook!("ws2_32.dll", WSASEND, WSASend( + fd: SOCKET, + buf: *const WSABUF, + dwbuffercount: c_uint, + lpnumberofbytesrecvd: *mut c_uint, + dwflags : c_uint, + lpoverlapped: *mut OVERLAPPED, + lpcompletionroutine : LPWSAOVERLAPPED_COMPLETION_ROUTINE + ) -> c_int); + impl_hook!("ws2_32.dll", WSASOCKETW, WSASocketW( + domain: c_int, + ty: WINSOCK_SOCKET_TYPE, + protocol: IPPROTO, + lpprotocolinfo: *const WSAPROTOCOL_INFOW, + g: c_uint, + dw_flags: c_uint + ) -> SOCKET); + // Enable the hook + minhook::MinHook::enable_all_hooks() + .map_err(|_| Error::new(ErrorKind::Other, "init all hooks failed !")) +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 00000000..f44bfc73 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "open-coroutine-macros" +version.workspace = true +edition.workspace = true +description = "The proc macros for open-coroutine" +repository.workspace = true +keywords = ["open-coroutine", "macro"] +categories = ["concurrency", "asynchronous", "os", "network-programming", "wasm"] +license.workspace = true +readme.workspace = true + +[dependencies] +syn = { workspace = true, features = ["full"] } +quote.workspace = true + +[lib] +proc-macro = true \ No newline at end of file diff --git a/open-coroutine-macros/src/lib.rs b/macros/src/lib.rs similarity index 69% rename from open-coroutine-macros/src/lib.rs rename to macros/src/lib.rs index 82066109..8b623975 100644 --- a/open-coroutine-macros/src/lib.rs +++ b/macros/src/lib.rs @@ -6,9 +6,9 @@ // elided_lifetimes_in_paths, // allow anonymous lifetime missing_copy_implementations, missing_debug_implementations, - // missing_docs, // TODO: add documents - // single_use_lifetimes, // TODO: fix lifetime names only used once - // trivial_casts, + missing_docs, // TODO: add documents + single_use_lifetimes, // TODO: fix lifetime names only used once + trivial_casts, // TODO: remove trivial casts in code trivial_numeric_casts, // unreachable_pub, allow clippy::redundant_pub_crate lint instead // unsafe_code, @@ -26,6 +26,7 @@ clippy::pedantic, // clippy::nursery, // It's still under development clippy::cargo, + unreachable_pub, )] #![allow( // Some explicitly allowed Clippy lints, must have clear reason to allow @@ -44,22 +45,21 @@ clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix clippy::single_char_lifetime_names, // TODO: change lifetime names )] - -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; +//! see `https://github.com/acl-dev/open-coroutine` use proc_macro::TokenStream; -use syn::{ItemFn, LitInt}; +use quote::quote; +use syn::{parse_macro_input, ItemFn, LitBool, LitInt}; +/// use this macro like `#[open_coroutine::main(event_loop_size = 2, max_size = 2, keep_alive_time = 0)]`. #[proc_macro_attribute] pub fn main(args: TokenStream, func: TokenStream) -> TokenStream { - let mut event_loop_size = num_cpus::get(); - let mut stack_size = 64 * 1024usize; - let mut min_size = 0usize; - let mut max_size = 65536usize; - let mut keep_alive_time = 0u64; + let mut event_loop_size = usize::MAX; + let mut stack_size = usize::MAX; + let mut min_size = usize::MAX; + let mut max_size = usize::MAX; + let mut keep_alive_time = u64::MAX; + let mut hook = true; if !args.is_empty() { let tea_parser = syn::meta::parser(|meta| { if meta.path.is_ident("event_loop_size") { @@ -72,6 +72,8 @@ pub fn main(args: TokenStream, func: TokenStream) -> TokenStream { max_size = meta.value()?.parse::()?.base10_parse()?; } else if meta.path.is_ident("keep_alive_time") { keep_alive_time = meta.value()?.parse::()?.base10_parse()?; + } else if meta.path.is_ident("hook") { + hook = meta.value()?.parse::()?.value(); } Ok(()) }); @@ -91,12 +93,25 @@ pub fn main(args: TokenStream, func: TokenStream) -> TokenStream { let caller = quote! { // rebuild the function, add a func named is_expired to check user login session expire or not. #func_vis fn #func_name #func_generics(#func_inputs) #func_output { - let open_coroutine_config = open_coroutine::Config::default(); - open_coroutine_config.set_event_loop_size(#event_loop_size) - .set_stack_size(#stack_size) - .set_min_size(#min_size) - .set_max_size(#max_size) - .set_keep_alive_time(#keep_alive_time); + let mut open_coroutine_config = open_coroutine::Config::default(); + if #event_loop_size != usize::MAX { + open_coroutine_config.set_event_loop_size(#event_loop_size); + } + if #stack_size != usize::MAX { + open_coroutine_config.set_stack_size(#stack_size); + } + if #min_size != usize::MAX { + open_coroutine_config.set_min_size(#min_size); + } + if #max_size != usize::MAX { + open_coroutine_config.set_max_size(#max_size); + } + if #keep_alive_time != u64::MAX { + open_coroutine_config.set_keep_alive_time(#keep_alive_time); + } + if #hook != true { + open_coroutine_config.set_hook(#hook); + } open_coroutine::init(open_coroutine_config); let _open_coroutine_result = #func_block; open_coroutine::shutdown(); diff --git a/open-coroutine-core/Cargo.toml b/open-coroutine-core/Cargo.toml deleted file mode 100644 index f19e1290..00000000 --- a/open-coroutine-core/Cargo.toml +++ /dev/null @@ -1,95 +0,0 @@ -[package] -name = "open-coroutine-core" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The open-coroutine is a simple, efficient and generic coroutine library." -repository = "https://github.com/acl-dev/open-coroutine" -keywords = ["runtime", "coroutine", "hook", "preempt", "work-steal"] -categories = ["concurrency", "asynchronous", "os", "network-programming", "wasm"] -license = "Apache-2.0" -readme = "../README.md" - -[dependencies] -# log -log = { version = "0.4.20", optional = true } -simplelog = { version = "0.12.2", optional = true } -time = { version = "0.3.36", features = [ - "formatting", - "macros", -], optional = true } -# common -cfg-if = "1.0.0" -# coroutine -corosensei = { version = "0.1.4", optional = true } -uuid = { version = "1.8.0", features = [ - "v4", - "fast-rng", - "macro-diagnostics", -] } -dashmap = "6.0.1" -# scheduler -once_cell = "1.18.0" -open-coroutine-timer = { version = "0.5.0", path = "../open-coroutine-timer" } -open-coroutine-queue = { version = "0.5.0", path = "../open-coroutine-queue" } -# coroutine pool -crossbeam-deque = "0.8.3" -# monitor -core_affinity = "0.8.1" -# net -crossbeam-utils = { version = "0.8.16", optional = true } -num_cpus = { version = "1.16.0", optional = true } -open-coroutine-iouring = { version = "0.5.0", path = "../open-coroutine-iouring", optional = true } - -[target.'cfg(unix)'.dependencies] -# coroutine -libc = "0.2.150" -nix = { version = "0.29.0", features = ["signal"] } -mio = { version = "1.0.0", default-features = false, features = [ - "net", - "os-poll", - "os-ext", -], optional = true } - -[target.'cfg(windows)'.dependencies] -# common -windows-sys = { version = "0.59.0", features = [ - "Win32_Foundation", - "Win32_System_Kernel", - "Win32_System_Threading", - "Win32_System_SystemInformation", - "Win32_System_Diagnostics_Debug", -] } -retour = { version = "0.3.1", features = ["static-detour"] } -polling = { version = "2.8.0", optional = true } - -[dev-dependencies] -backtrace = "0.3.69" - -[features] -default = ["syscall", "preemptive-schedule", "logs"] - -# Print some help log. -# Enable for default. -logs = ["log", "simplelog", "time"] - -korosensei = ["corosensei", "nix/pthread"] - -boost = [] - -# Provide preemptive scheduling implementation. -# Enable for default. -preemptive-schedule = ["korosensei"] - -# Provide net API abstraction and implementation. -net = ["korosensei", "num_cpus", "crossbeam-utils", "polling", "mio"] - -# Provide io_uring abstraction and implementation. -# This feature only works in linux. -io_uring = ["net", "open-coroutine-iouring"] - -# Provide syscall implementation. -syscall = ["net"] - -# Enable all features -full = ["syscall", "preemptive-schedule", "io_uring", "logs"] diff --git a/open-coroutine-core/examples/preemptive.rs b/open-coroutine-core/examples/preemptive.rs deleted file mode 100644 index a991dc65..00000000 --- a/open-coroutine-core/examples/preemptive.rs +++ /dev/null @@ -1,85 +0,0 @@ -fn main() -> std::io::Result<()> { - cfg_if::cfg_if! { - if #[cfg(all(unix, feature = "preemptive-schedule"))] { - use open_coroutine_core::scheduler::Scheduler; - use std::sync::{Arc, Condvar, Mutex}; - use std::time::Duration; - - static mut TEST_FLAG1: bool = true; - static mut TEST_FLAG2: bool = true; - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("preemptive".to_string()) - .spawn(move || { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - println!("coroutine1 launched"); - while unsafe { TEST_FLAG1 } { - println!("loop1"); - _ = unsafe { libc::usleep(10_000) }; - } - println!("loop1 end"); - None - }, - None, - ); - _ = scheduler.submit_co( - |_, _| { - println!("coroutine2 launched"); - while unsafe { TEST_FLAG2 } { - println!("loop2"); - _ = unsafe { libc::usleep(10_000) }; - } - println!("loop2 end"); - unsafe { TEST_FLAG1 = false }; - None - }, - None, - ); - _ = scheduler.submit_co( - |_, _| { - println!("coroutine3 launched"); - unsafe { TEST_FLAG2 = false }; - None - }, - None, - ); - scheduler.try_schedule().unwrap(); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "preemptive schedule failed", - )) - } else { - unsafe { - handler.join().unwrap(); - assert!(!TEST_FLAG1, "preemptive schedule failed"); - } - Ok(()) - } - } else { - println!("please enable preemptive-schedule feature"); - Ok(()) - } - } -} diff --git a/open-coroutine-core/src/common.rs b/open-coroutine-core/src/common.rs deleted file mode 100644 index bc778a15..00000000 --- a/open-coroutine-core/src/common.rs +++ /dev/null @@ -1,400 +0,0 @@ -use crate::constants::PoolState; -use crate::scheduler::SchedulableSuspender; -use std::fmt::Debug; -use std::io::{Error, ErrorKind}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::time::Duration; - -#[allow(clippy::pedantic, missing_docs)] -pub fn page_size() -> usize { - static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); - let mut ret = PAGE_SIZE.load(Ordering::Relaxed); - if ret == 0 { - unsafe { - cfg_if::cfg_if! { - if #[cfg(windows)] { - let mut info = std::mem::zeroed(); - windows_sys::Win32::System::SystemInformation::GetSystemInfo(&mut info); - ret = info.dwPageSize as usize - } else { - ret = libc::sysconf(libc::_SC_PAGESIZE) as usize; - } - } - } - PAGE_SIZE.store(ret, Ordering::Relaxed); - } - ret -} - -/// Catch panic. -#[macro_export] -macro_rules! catch { - ($f:expr, $msg:expr, $arg1:expr, $arg2:expr) => { - std::panic::catch_unwind(std::panic::AssertUnwindSafe($f)).map_err(|e| { - let message = *e.downcast_ref::<&'static str>().unwrap_or(&$msg); - $crate::error!("{} {} error:{}", $arg1, $arg2, message); - message - }) - }; -} - -/// Fast impl `Display` trait for `Debug` types. -/// Check for help information. -#[macro_export] -macro_rules! impl_display_by_debug { - ($struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? - $(where $( - $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? - ),+)? - ) => { - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? std::fmt::Display - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: std::fmt::Debug, - { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } - } - }; -} - -/// Give the object a name. -pub trait Named { - /// Get the name of this object. - fn get_name(&self) -> &str; -} - -/// Fast impl common traits for `Named` types. -/// Check for help information. -#[macro_export] -macro_rules! impl_for_named { - ($struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? - $(where $( - $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? - ),+)? - ) => { - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? Eq - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: $crate::common::Named, - { - } - - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? PartialEq - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: $crate::common::Named, - { - fn eq(&self, other: &Self) -> bool { - self.get_name().eq(other.get_name()) - } - } - - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? Ord - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: $crate::common::Named, - { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.get_name().cmp(other.get_name()) - } - } - - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? PartialOrd - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: $crate::common::Named, - { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? std::hash::Hash - for $struct_name$(<$($generic1),+>)? - where - $($($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+,)? - $struct_name$(<$($generic1),+>)?: $crate::common::Named, - { - fn hash(&self, state: &mut H) { - self.get_name().hash(state) - } - } - }; -} - -/// A trait implemented for which needs `current()`. -pub trait Current { - /// Init the current. - fn init_current(current: &Self) - where - Self: Sized; - - /// Get the current if it has. - fn current<'c>() -> Option<&'c Self> - where - Self: Sized; - - /// clean the current. - fn clean_current() - where - Self: Sized; -} - -/// Fast impl `Current` for a type. -/// This crate use `std` cause `#![no_std]` not support `thread_local!`. -/// Check for help information. -#[macro_export] -macro_rules! impl_current_for { - ( - $name:ident, - $struct_name:ident$(<$($generic1:tt $( : $trait_tt1: tt $( + $trait_tt2: tt)*)?),+>)? - $(where $( - $generic2:tt $( : $trait_tt3: tt $( + $trait_tt4: tt)*)? - ),+)? - ) => { - thread_local! { - static $name: std::cell::RefCell> = const { std::cell::RefCell::new(std::collections::VecDeque::new()) }; - } - - impl$(<$($generic1 $( : $trait_tt1 $( + $trait_tt2)*)?),+>)? $crate::common::Current for $struct_name$(<$($generic1),+>)? - $(where $($generic2 $( : $trait_tt3 $( + $trait_tt4)*)?),+)? - { - fn init_current(current: &Self) { - $name.with(|s| { - s.borrow_mut() - .push_front(core::ptr::from_ref(current).cast::()); - }); - } - - fn current<'current>() -> Option<&'current Self> { - $name.with(|s| { - s.borrow() - .front() - .map(|ptr| unsafe { &*(*ptr).cast::() }) - }) - } - - fn clean_current() { - $name.with(|s| _ = s.borrow_mut().pop_front()); - } - } - }; -} - -/// A trait for blocking current thread. -pub trait Blocker: Debug + Named { - /// Block current thread for a while. - fn block(&self, dur: Duration); -} - -#[allow(missing_docs)] -#[derive(Debug, Default)] -pub struct CondvarBlocker(std::sync::Mutex<()>, std::sync::Condvar); - -/// const `CONDVAR_BLOCKER_NAME`. -pub const CONDVAR_BLOCKER_NAME: &str = "CondvarBlocker"; - -impl Named for CondvarBlocker { - fn get_name(&self) -> &str { - CONDVAR_BLOCKER_NAME - } -} - -impl Blocker for CondvarBlocker { - fn block(&self, dur: Duration) { - _ = self.1.wait_timeout(self.0.lock().unwrap(), dur); - } -} - -#[allow(missing_docs)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] -pub struct DelayBlocker {} - -/// const `DELAY_BLOCKER_NAME`. -pub const DELAY_BLOCKER_NAME: &str = "DelayBlocker"; - -impl Named for DelayBlocker { - fn get_name(&self) -> &str { - DELAY_BLOCKER_NAME - } -} - -impl Blocker for DelayBlocker { - fn block(&self, dur: Duration) { - if let Some(suspender) = SchedulableSuspender::current() { - suspender.delay(dur); - } - } -} - -/// Join abstraction. -pub trait JoinHandler { - /// create `JoinHandle` instance. - #[must_use] - fn err() -> Self - where - Self: Sized, - { - Self::new(std::ptr::null(), "") - } - - /// create `JoinHandle` instance. - fn new(t: *const T, name: &str) -> Self; - - /// get the task name. - /// - /// # Errors - /// if the task name is invalid. - fn get_name(&self) -> std::io::Result<&str>; - - /// join with `Duration`. - /// - /// # Errors - /// see `timeout_at_join`. - fn timeout_join(&self, dur: Duration) -> std::io::Result, &str>> { - self.timeout_at_join(open_coroutine_timer::get_timeout_time(dur)) - } - - /// join. - /// - /// # Errors - /// see `timeout_at_join`. - fn join(&self) -> std::io::Result, &str>> { - self.timeout_at_join(u64::MAX) - } - - /// join with timeout. - /// - /// # Errors - /// if join failed. - fn timeout_at_join(&self, timeout_time: u64) -> std::io::Result, &str>>; -} - -/// The `Pool` abstraction. -pub trait Pool: Debug { - /// Set the minimum number in this pool (the meaning of this number - /// depends on the specific implementation). - fn set_min_size(&self, min_size: usize); - - /// Get the minimum number in this pool (the meaning of this number - /// depends on the specific implementation). - fn get_min_size(&self) -> usize; - - /// Gets the number currently running in this pool. - fn get_running_size(&self) -> usize; - - /// Set the maximum number in this pool (the meaning of this number - /// depends on the specific implementation). - fn set_max_size(&self, max_size: usize); - - /// Get the maximum number in this pool (the meaning of this number - /// depends on the specific implementation). - fn get_max_size(&self) -> usize; - - /// Set the maximum idle time running in this pool. - /// `keep_alive_time` has `ns` units. - fn set_keep_alive_time(&self, keep_alive_time: u64); - - /// Get the maximum idle time running in this pool. - /// Returns in `ns` units. - fn get_keep_alive_time(&self) -> u64; -} - -/// The `StatePool` abstraction. -pub trait StatePool: Pool + Named { - /// Get the state of this pool. - fn state(&self) -> PoolState; - - /// Change the state of this pool. - fn change_state(&self, state: PoolState) -> PoolState; - - /// created -> running - /// - /// # Errors - /// if change state fails. - fn running(&self, sync: bool) -> std::io::Result<()> { - let current = self.state(); - match current { - PoolState::Created => { - let state = PoolState::Running(sync); - _ = self.change_state(state); - crate::info!("{} {:?}->{:?}", self.get_name(), current, state); - return Ok(()); - } - PoolState::Running(pre) => { - if pre != sync { - let state = PoolState::Running(sync); - _ = self.change_state(state); - crate::info!("{} {:?}->{:?}", self.get_name(), current, state); - } - return Ok(()); - } - _ => {} - } - Err(Error::new( - ErrorKind::Other, - format!( - "{} unexpected {current}->{:?}", - self.get_name(), - PoolState::Running(sync) - ), - )) - } - - /// running -> stopping - /// stopping -> stopped - /// - /// # Errors - /// if change state fails. - fn end(&self) -> std::io::Result<()> { - let current = self.state(); - match current { - PoolState::Running(sync) => { - let state = PoolState::Stopping(sync); - _ = self.change_state(state); - crate::info!("{} {:?}->{:?}", self.get_name(), current, state); - return Ok(()); - } - PoolState::Stopping(_) => { - let state = PoolState::Stopped; - _ = self.change_state(state); - crate::info!("{} {:?}->{:?}", self.get_name(), current, state); - return Ok(()); - } - PoolState::Stopped => return Ok(()), - PoolState::Created => {} - } - Err(Error::new( - ErrorKind::Other, - format!( - "{} unexpected {current}->{:?}", - self.get_name(), - PoolState::Stopped - ), - )) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::cmp::Ordering; - - #[test] - fn condvar_blocker() { - let blocker = CondvarBlocker::default(); - let time = open_coroutine_timer::now(); - blocker.block(Duration::from_secs(1)); - let cost = Duration::from_nanos(open_coroutine_timer::now().saturating_sub(time)); - if Ordering::Less == cost.cmp(&Duration::from_secs(1)) { - crate::error!("condvar_blocker cost {cost:?}"); - } - } -} diff --git a/open-coroutine-core/src/coroutine/listener.rs b/open-coroutine-core/src/coroutine/listener.rs deleted file mode 100644 index 4ebda710..00000000 --- a/open-coroutine-core/src/coroutine/listener.rs +++ /dev/null @@ -1,163 +0,0 @@ -#[cfg(feature = "log")] -use crate::common::Named; -use crate::constants::CoroutineState; -use crate::coroutine::Coroutine; -use std::fmt::Debug; -use std::panic::UnwindSafe; - -/// A trait mainly used for monitors. -#[allow(unused_variables)] -pub trait Listener: Debug -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ - /// Callback when the coroutine is created. - fn on_create(&self, co: &Coroutine, stack_size: usize) {} - - /// Callback after changing the status of coroutine. - fn on_state_changed( - &self, - co: &Coroutine, - old_state: CoroutineState, - new_state: CoroutineState, - ) { - } - - /// Callback after changing the coroutine status to ready. - fn on_ready( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - } - - /// Callback after changing the coroutine status to running. - fn on_running( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - } - - /// Callback after changing the coroutine status to suspend. - fn on_suspend( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - } - - /// callback when the coroutine enters syscall. - fn on_syscall( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - } - - /// Callback when the coroutine is completed. - fn on_complete( - &self, - co: &Coroutine, - old_state: CoroutineState, - result: Return, - ) { - } - - /// Callback when the coroutine is completed with errors, usually, panic occurs. - fn on_error( - &self, - co: &Coroutine, - old_state: CoroutineState, - message: &str, - ) { - } -} - -macro_rules! invoke_listeners { - ($self:expr, $method_name:expr, $method:ident($( $args:expr ),*)) => { - for listener in &$self.listeners { - _ = $crate::catch!(|| listener.$method( $( $args ),* ), - "Listener failed without message", - ($( $args ),*).0.get_name(), - $method_name - ); - } - } -} - -impl Listener for Coroutine<'_, Param, Yield, Return> -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ - fn on_create(&self, co: &Coroutine, stack_size: usize) { - invoke_listeners!(self, "on_create", on_create(co, stack_size)); - } - - fn on_state_changed( - &self, - co: &Coroutine, - old_state: CoroutineState, - new_state: CoroutineState, - ) { - invoke_listeners!( - self, - "on_state_changed", - on_state_changed(co, old_state, new_state) - ); - } - - fn on_ready( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - invoke_listeners!(self, "on_ready", on_ready(co, old_state)); - } - - fn on_running( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - invoke_listeners!(self, "on_running", on_running(co, old_state)); - } - - fn on_suspend( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - invoke_listeners!(self, "on_suspend", on_suspend(co, old_state)); - } - - fn on_syscall( - &self, - co: &Coroutine, - old_state: CoroutineState, - ) { - invoke_listeners!(self, "on_syscall", on_syscall(co, old_state)); - } - - fn on_complete( - &self, - co: &Coroutine, - old_state: CoroutineState, - result: Return, - ) { - invoke_listeners!(self, "on_complete", on_complete(co, old_state, result)); - } - - fn on_error( - &self, - co: &Coroutine, - old_state: CoroutineState, - message: &str, - ) { - invoke_listeners!(self, "on_error", on_error(co, old_state, message)); - } -} diff --git a/open-coroutine-core/src/coroutine/tests.rs b/open-coroutine-core/src/coroutine/tests.rs deleted file mode 100644 index 589c1f74..00000000 --- a/open-coroutine-core/src/coroutine/tests.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::*; -use crate::coroutine::suspender::Suspender; - -#[test] -fn test_return() { - let mut coroutine = co!(|_: &Suspender<'_, (), i32>, _| { 1 }); - assert_eq!(CoroutineState::Complete(1), coroutine.resume().unwrap()); -} - -#[test] -fn test_yield_once() { - let mut coroutine = co!(|suspender: &Suspender<'_, i32, i32>, param| { - assert_eq!(1, param); - _ = suspender.suspend_with(2); - }); - assert_eq!( - CoroutineState::Suspend(2, 0), - coroutine.resume_with(1).unwrap() - ); -} - -#[test] -fn test_yield() { - let mut coroutine = co!(|suspender, input| { - assert_eq!(1, input); - assert_eq!(3, suspender.suspend_with(2)); - assert_eq!(5, suspender.suspend_with(4)); - 6 - }); - assert_eq!( - CoroutineState::Suspend(2, 0), - coroutine.resume_with(1).unwrap() - ); - assert_eq!( - CoroutineState::Suspend(4, 0), - coroutine.resume_with(3).unwrap() - ); - assert_eq!( - CoroutineState::Complete(6), - coroutine.resume_with(5).unwrap() - ); -} - -#[test] -fn test_current() { - assert!(Coroutine::::current().is_none()); - let parent_name = "parent"; - let mut parent = co!( - String::from(parent_name), - |_: &Suspender<'_, i32, i32>, input| { - assert_eq!(0, input); - assert_eq!( - parent_name, - Coroutine::::current().unwrap().get_name() - ); - assert_eq!( - parent_name, - Coroutine::::current().unwrap().get_name() - ); - - let child_name = "child"; - let mut child = co!( - String::from(child_name), - |_: &Suspender<'_, i32, i32>, input| { - assert_eq!(0, input); - assert_eq!( - child_name, - Coroutine::::current().unwrap().get_name() - ); - assert_eq!( - child_name, - Coroutine::::current().unwrap().get_name() - ); - 1 - } - ); - assert_eq!(CoroutineState::Complete(1), child.resume_with(0).unwrap()); - - assert_eq!( - parent_name, - Coroutine::::current().unwrap().get_name() - ); - assert_eq!( - parent_name, - Coroutine::::current().unwrap().get_name() - ); - 1 - } - ); - assert_eq!(CoroutineState::Complete(1), parent.resume_with(0).unwrap()); -} - -#[test] -fn test_backtrace() { - let mut coroutine = co!(|suspender, input| { - assert_eq!(1, input); - println!("{:?}", backtrace::Backtrace::new()); - assert_eq!(3, suspender.suspend_with(2)); - println!("{:?}", backtrace::Backtrace::new()); - 4 - }); - assert_eq!( - CoroutineState::Suspend(2, 0), - coroutine.resume_with(1).unwrap() - ); - assert_eq!( - CoroutineState::Complete(4), - coroutine.resume_with(3).unwrap() - ); -} - -#[test] -fn test_context() { - let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { - let current = Coroutine::<(), (), ()>::current().unwrap(); - assert_eq!(2, *current.get("1").unwrap()); - *current.get_mut("1").unwrap() = 3; - () - }); - assert!(coroutine.put("1", 1).is_none()); - assert_eq!(Some(1), coroutine.put("1", 2)); - assert_eq!(CoroutineState::Complete(()), coroutine.resume().unwrap()); - assert_eq!(Some(3), coroutine.remove("1")); -} - -#[test] -fn test_panic() { - let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { - panic!("test panic, just ignore it"); - }); - let result = coroutine.resume(); - assert!(result.is_ok()); - let error = match result.unwrap() { - CoroutineState::Error(_) => true, - _ => false, - }; - assert!(error); -} - -#[test] -fn test_trap() { - let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { - println!("Before trap"); - unsafe { std::ptr::write_volatile(1 as *mut u8, 0) }; - println!("After trap"); - }); - let result = coroutine.resume(); - assert!(result.is_ok()); - let error = match result.unwrap() { - CoroutineState::Error(_) => true, - _ => false, - }; - assert!(error); -} - -#[cfg(not(debug_assertions))] -#[test] -fn test_invalid_memory_reference() { - let mut coroutine = co!(|_: &Suspender<'_, (), ()>, ()| { - println!("Before invalid memory reference"); - // 没有加--release运行,会收到SIGABRT信号,不好处理,直接禁用测试 - unsafe { - let co = &*((1usize as *mut std::ffi::c_void).cast::>()); - println!("{}", co.state()); - } - println!("After invalid memory reference"); - }); - let result = coroutine.resume(); - assert!(result.is_ok()); - println!("{:?}", result); - let error = match result.unwrap() { - CoroutineState::Error(_) => true, - _ => false, - }; - assert!(error); -} diff --git a/open-coroutine-core/src/log.rs b/open-coroutine-core/src/log.rs deleted file mode 100644 index 487efb2e..00000000 --- a/open-coroutine-core/src/log.rs +++ /dev/null @@ -1,98 +0,0 @@ -#[cfg(feature = "logs")] -pub static LOG: std::sync::Once = std::sync::Once::new(); - -#[macro_export] -macro_rules! init_log { - () => { - $crate::log::LOG.call_once(|| { - let mut builder = simplelog::ConfigBuilder::new(); - let result = builder.set_time_format_rfc2822().set_time_offset_to_local(); - let config = if let Ok(builder) = result { - builder - } else { - result.unwrap_err() - } - .build(); - _ = simplelog::CombinedLogger::init(vec![simplelog::TermLogger::new( - log::LevelFilter::Info, - config, - simplelog::TerminalMode::Mixed, - simplelog::ColorChoice::Auto, - )]); - }); - }; -} - -#[macro_export] -macro_rules! info { - // info!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") - // info!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(target: $target, log::Level::Info, $($arg)+) - } - } - }; - - // info!("a {} event", "log") - ($($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(log::Level::Info, $($arg)+) - } - } - } -} - -#[macro_export] -macro_rules! warn { - // warn!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") - // warn!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(target: $target, log::Level::Warn, $($arg)+) - } - } - }; - - // warn!("a {} event", "log") - ($($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(log::Level::Warn, $($arg)+) - } - } - } -} - -#[macro_export] -macro_rules! error { - // error!(target: "my_target", key1 = 42, key2 = true; "a {} event", "log") - // error!(target: "my_target", "a {} event", "log") - (target: $target:expr, $($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(target: $target, log::Level::Error, $($arg)+) - } - } - - }; - - // error!("a {} event", "log") - ($($arg:tt)+) => { - cfg_if::cfg_if! { - if #[cfg(feature = "logs")] { - $crate::init_log!(); - log::log!(log::Level::Error, $($arg)+) - } - } - - } -} diff --git a/open-coroutine-core/src/monitor/creator.rs b/open-coroutine-core/src/monitor/creator.rs deleted file mode 100644 index ebad5dff..00000000 --- a/open-coroutine-core/src/monitor/creator.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::common::Current; -use crate::constants::CoroutineState; -use crate::coroutine::listener::Listener; -use crate::coroutine::Coroutine; -use crate::monitor::Monitor; -use open_coroutine_timer::get_timeout_time; -use std::fmt::Debug; -use std::panic::UnwindSafe; -use std::time::Duration; - -#[repr(C)] -#[derive(Debug, Default)] -pub(crate) struct MonitorListener {} - -const NOTIFY_NODE: &str = "MONITOR_NODE"; - -impl Listener for MonitorListener -where - Param: UnwindSafe, - Yield: Debug + Copy + UnwindSafe, - Return: Debug + Copy + UnwindSafe, -{ - fn on_state_changed( - &self, - co: &Coroutine, - _: CoroutineState, - new_state: CoroutineState, - ) { - if Monitor::current().is_some() { - return; - } - match new_state { - CoroutineState::Created | CoroutineState::Ready => {} - CoroutineState::Running => { - let timestamp = get_timeout_time(Duration::from_millis(10)); - if let Ok(node) = Monitor::submit(timestamp) { - _ = co.put(NOTIFY_NODE, node); - } - } - CoroutineState::Suspend(_, _) - | CoroutineState::SystemCall(_, _, _) - | CoroutineState::Complete(_) - | CoroutineState::Error(_) => { - if let Some(node) = co.get(NOTIFY_NODE) { - _ = Monitor::remove(node); - } - } - } - } -} diff --git a/open-coroutine-core/src/monitor/mod.rs b/open-coroutine-core/src/monitor/mod.rs deleted file mode 100644 index 038e59e1..00000000 --- a/open-coroutine-core/src/monitor/mod.rs +++ /dev/null @@ -1,195 +0,0 @@ -use crate::common::Current; -use crate::constants::{MonitorState, MONITOR_CPU}; -use crate::monitor::node::NotifyNode; -#[cfg(feature = "net")] -use crate::net::event_loop::EventLoops; -use crate::scheduler::SchedulableSuspender; -use crate::{error, impl_current_for, warn}; -use core_affinity::{set_for_current, CoreId}; -use nix::sys::pthread::pthread_kill; -use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; -use once_cell::sync::Lazy; -use open_coroutine_timer::now; -use std::cell::{Cell, UnsafeCell}; -use std::collections::HashSet; -use std::io::{Error, ErrorKind}; -use std::mem::MaybeUninit; -use std::panic::AssertUnwindSafe; -use std::sync::atomic::Ordering; -use std::thread::JoinHandle; -use std::time::Duration; - -mod node; - -pub(crate) mod creator; - -static mut GLOBAL: Lazy = Lazy::new(Monitor::new); - -#[repr(C)] -#[derive(Debug)] -pub(crate) struct Monitor { - cpu: usize, - notify_queue: UnsafeCell>, - state: Cell, - thread: UnsafeCell>>, -} - -impl Drop for Monitor { - fn drop(&mut self) { - if !std::thread::panicking() { - assert!( - self.notify_queue.get_mut().is_empty(), - "there are still timer tasks to be carried out !" - ); - assert!( - unsafe { self.thread.get_mut().assume_init_mut().is_finished() }, - "the monitor thread not finished !" - ); - } - } -} - -impl_current_for!(MONITOR, Monitor); - -impl Monitor { - #[cfg(unix)] - fn register_handler(sigurg_handler: extern "C" fn(libc::c_int)) { - // install SIGURG signal handler - let mut set = SigSet::empty(); - set.add(Signal::SIGURG); - let sa = SigAction::new( - SigHandler::Handler(sigurg_handler), - SaFlags::SA_RESTART, - set, - ); - unsafe { _ = sigaction(Signal::SIGURG, &sa).unwrap() }; - } - - fn monitor_thread_main() { - // todo pin this thread to the CPU core closest to the network card - if set_for_current(CoreId { id: MONITOR_CPU }) { - warn!("pin monitor thread to CPU core-{MONITOR_CPU} failed !"); - } - let monitor = Monitor::get_instance(); - let notify_queue = unsafe { &*monitor.notify_queue.get() }; - while MonitorState::Running == monitor.state.get() || !notify_queue.is_empty() { - //只遍历,不删除,如果抢占调度失败,会在1ms后不断重试,相当于主动检测 - for node in notify_queue { - if now() < node.timestamp() { - continue; - } - //实际上只对陷入重度计算的协程发送信号抢占 - //对于陷入执行系统调用的协程不发送信号(如果发送信号,会打断系统调用,进而降低总体性能) - if pthread_kill(node.pthread(), Signal::SIGURG).is_err() { - error!( - "Attempt to preempt scheduling for thread:{} failed !", - node.pthread() - ); - } - } - cfg_if::cfg_if! { - if #[cfg(feature = "net")] { - //monitor线程不执行协程计算任务,每次循环至少wait 1ms - let event_loop = EventLoops::monitor(); - _ = event_loop.wait_just(Some(Duration::from_millis(1))); - //push tasks to other event-loop - while let Some(task) = event_loop.pop() { - EventLoops::submit_raw(task); - } - } - } - } - } - - fn new() -> Self { - //初始化monitor线程 - Monitor { - cpu: MONITOR_CPU, - notify_queue: UnsafeCell::default(), - state: Cell::new(MonitorState::Created), - thread: UnsafeCell::new(MaybeUninit::uninit()), - } - } - - fn get_instance() -> &'static Monitor { - unsafe { &*std::ptr::addr_of!(GLOBAL) } - } - - fn start(&self) -> std::io::Result<()> { - extern "C" fn sigurg_handler(_: libc::c_int) { - if let Ok(mut set) = SigSet::thread_get_mask() { - //删除对SIGURG信号的屏蔽,使信号处理函数即使在处理中,也可以再次进入信号处理函数 - set.remove(Signal::SIGURG); - set.thread_set_mask() - .expect("Failed to remove SIGURG signal mask!"); - if let Some(suspender) = SchedulableSuspender::current() { - suspender.suspend(); - } - } - } - match self.state.get() { - MonitorState::Created => { - self.state.set(MonitorState::Running); - // install SIGURG signal handler - Monitor::register_handler(sigurg_handler); - // start the monitor thread - let monitor = unsafe { &mut *self.thread.get() }; - *monitor = MaybeUninit::new( - std::thread::Builder::new() - .name("open-coroutine-monitor".to_string()) - .spawn(|| { - Monitor::init_current(Monitor::get_instance()); - #[allow(unused_variables)] - if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe( - Monitor::monitor_thread_main, - )) { - #[cfg(feature = "logs")] - let message = *e - .downcast_ref::<&'static str>() - .unwrap_or(&"Monitor failed without message"); - error!("open-coroutine-monitor exited with error:{}", message); - } else { - warn!("open-coroutine-monitor has exited"); - } - Monitor::clean_current(); - })?, - ); - Ok(()) - } - MonitorState::Running => Ok(()), - MonitorState::Stopping | MonitorState::Stopped => Err(Error::new( - ErrorKind::Unsupported, - "Restart operation is unsupported !", - )), - } - } - - #[allow(dead_code)] - pub fn stop() { - Self::get_instance().state.set(MonitorState::Stopping); - cfg_if::cfg_if! { - if #[cfg(feature = "net")] { - let pair = EventLoops::new_condition(); - let (lock, cvar) = pair.as_ref(); - let pending = lock.lock().unwrap(); - _ = pending.fetch_add(1, Ordering::Release); - cvar.notify_one(); - } - } - } - - fn submit(timestamp: u64) -> std::io::Result { - let instance = Self::get_instance(); - instance.start()?; - let queue = unsafe { &mut *instance.notify_queue.get() }; - let node = NotifyNode::new(timestamp); - _ = queue.insert(node); - Ok(node) - } - - fn remove(node: &NotifyNode) -> bool { - let instance = Self::get_instance(); - let queue = unsafe { &mut *instance.notify_queue.get() }; - queue.remove(node) - } -} diff --git a/open-coroutine-core/src/monitor/node.rs b/open-coroutine-core/src/monitor/node.rs deleted file mode 100644 index 03a55d0f..00000000 --- a/open-coroutine-core/src/monitor/node.rs +++ /dev/null @@ -1,25 +0,0 @@ -use nix::sys::pthread::{pthread_self, Pthread}; - -#[repr(C)] -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub(super) struct NotifyNode { - timestamp: u64, - pthread: Pthread, -} - -impl NotifyNode { - pub fn new(timestamp: u64) -> Self { - NotifyNode { - timestamp, - pthread: pthread_self(), - } - } - - pub fn timestamp(&self) -> u64 { - self.timestamp - } - - pub fn pthread(&self) -> Pthread { - self.pthread - } -} diff --git a/open-coroutine-core/src/net/config.rs b/open-coroutine-core/src/net/config.rs deleted file mode 100644 index 99c06c46..00000000 --- a/open-coroutine-core/src/net/config.rs +++ /dev/null @@ -1,126 +0,0 @@ -use crate::constants::DEFAULT_STACK_SIZE; -use crossbeam_utils::atomic::AtomicCell; -use once_cell::sync::Lazy; -use std::fmt::{Debug, Formatter}; - -static CONFIG: Lazy = Lazy::new(Config::default); - -#[repr(C)] -pub struct Config { - event_loop_size: AtomicCell, - stack_size: AtomicCell, - min_size: AtomicCell, - max_size: AtomicCell, - keep_alive_time: AtomicCell, -} - -impl Config { - #[must_use] - pub fn get_instance() -> &'static Config { - &CONFIG - } - - #[must_use] - pub fn get_event_loop_size(&self) -> usize { - self.event_loop_size.load() - } - - #[must_use] - pub fn get_stack_size(&self) -> usize { - self.stack_size.load() - } - - #[must_use] - pub fn get_min_size(&self) -> usize { - self.min_size.load() - } - - #[must_use] - pub fn get_max_size(&self) -> usize { - self.max_size.load() - } - - #[must_use] - pub fn get_keep_alive_time(&self) -> u64 { - self.keep_alive_time.load() - } - - pub fn set_event_loop_size(&self, event_loop_size: usize) -> &Self { - assert!( - event_loop_size > 1, - "event_loop_size must be greater than 1" - ); - self.event_loop_size.store(event_loop_size); - self - } - - pub fn set_stack_size(&self, stack_size: usize) -> &Self { - self.stack_size.store(stack_size); - self - } - - pub fn set_min_size(&self, min_size: usize) -> &Self { - self.min_size.store(min_size); - self - } - - pub fn set_max_size(&self, max_size: usize) -> &Self { - assert!(max_size > 0, "max_size must be greater than 0"); - assert!( - max_size >= self.min_size.load(), - "max_size must be greater than or equal to min_size" - ); - self.max_size.store(max_size); - self - } - - pub fn set_keep_alive_time(&self, keep_alive_time: u64) -> &Self { - self.keep_alive_time.store(keep_alive_time); - self - } -} - -impl Default for Config { - fn default() -> Self { - Config { - event_loop_size: AtomicCell::new(num_cpus::get()), - stack_size: AtomicCell::new(DEFAULT_STACK_SIZE), - min_size: AtomicCell::new(0), - max_size: AtomicCell::new(65536), - keep_alive_time: AtomicCell::new(0), - } - } -} - -impl Debug for Config { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Config") - .field("event_loop_size", &self.get_event_loop_size()) - .field("stack_size", &self.get_stack_size()) - .field("min_size", &self.get_min_size()) - .field("max_size", &self.get_max_size()) - .field("keep_alive_time", &self.get_keep_alive_time()) - .finish() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[ignore] - #[test] - fn test_config() { - _ = Config::get_instance() - .set_event_loop_size(2) - .set_stack_size(4096) - .set_min_size(256) - .set_max_size(256) - .set_keep_alive_time(0); - assert_eq!(2, CONFIG.event_loop_size.load()); - assert_eq!(4096, CONFIG.stack_size.load()); - assert_eq!(256, CONFIG.min_size.load()); - assert_eq!(256, CONFIG.max_size.load()); - assert_eq!(0, CONFIG.keep_alive_time.load()); - } -} diff --git a/open-coroutine-core/src/net/event_loop/blocker.rs b/open-coroutine-core/src/net/event_loop/blocker.rs deleted file mode 100644 index d771d634..00000000 --- a/open-coroutine-core/src/net/event_loop/blocker.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::common::{Blocker, Named}; -use crate::net::event_loop::core::EventLoop; -use std::time::Duration; - -#[repr(C)] -#[derive(Debug)] -pub(crate) struct SelectBlocker { - event_loop: &'static EventLoop, -} - -impl SelectBlocker { - pub(crate) fn new(event_loop: &mut EventLoop) -> Self { - SelectBlocker { - event_loop: unsafe { Box::leak(Box::from_raw(event_loop)) }, - } - } -} - -impl Named for SelectBlocker { - fn get_name(&self) -> &str { - "SelectBlocker" - } -} - -impl Blocker for SelectBlocker { - fn block(&self, time: Duration) { - _ = self.event_loop.wait_event(Some(time)); - } -} diff --git a/open-coroutine-core/src/net/event_loop/core.rs b/open-coroutine-core/src/net/event_loop/core.rs deleted file mode 100644 index e306bbde..00000000 --- a/open-coroutine-core/src/net/event_loop/core.rs +++ /dev/null @@ -1,523 +0,0 @@ -use crate::common::{Current, JoinHandler, Named}; -use crate::constants::{CoroutineState, SyscallState}; -use crate::coroutine::suspender::Suspender; -use crate::net::event_loop::blocker::SelectBlocker; -use crate::net::event_loop::join::{CoJoinHandle, TaskJoinHandle}; -use crate::net::selector::{Event, Events, Poller, Selector}; -use crate::pool::task::Task; -use crate::pool::CoroutinePool; -use crate::scheduler::{SchedulableCoroutine, SchedulableSuspender}; -use crate::{impl_current_for, impl_for_named}; -use open_coroutine_timer::get_timeout_time; -use std::ffi::{c_char, c_int, c_void, CStr, CString}; -use std::mem::MaybeUninit; -use std::ops::{Deref, DerefMut}; -use std::panic::UnwindSafe; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time::Duration; -use uuid::Uuid; - -cfg_if::cfg_if! { - if #[cfg(all(target_os = "linux", feature = "io_uring"))] { - use dashmap::DashMap; - use libc::{epoll_event, iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; - use once_cell::sync::Lazy; - use std::sync::{Arc, Condvar, Mutex}; - - macro_rules! io_uring_impl { - ( $invoker: expr , $syscall: ident, $($arg: expr),* $(,)* ) => {{ - let token = EventLoop::token(true); - $invoker - .$syscall(token, $($arg, )*) - .map(|()| { - let arc = Arc::new((Mutex::new(None), Condvar::new())); - assert!( - SYSCALL_WAIT_TABLE.insert(token, arc.clone()).is_none(), - "The previous token was not retrieved in a timely manner" - ); - arc - }) - }}; - } - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct EventLoop { - cpu: u32, - #[cfg(all(target_os = "linux", feature = "io_uring"))] - operator: open_coroutine_iouring::io_uring::IoUringOperator, - selector: Poller, - //是否正在执行select - waiting: AtomicBool, - //协程池 - pool: MaybeUninit>, -} - -impl Named for EventLoop { - fn get_name(&self) -> &str { - self.deref().get_name() - } -} - -impl_for_named!(EventLoop); - -impl_current_for!(EVENT_LOOP, EventLoop); - -#[allow(clippy::type_complexity)] -#[cfg(all(target_os = "linux", feature = "io_uring"))] -static SYSCALL_WAIT_TABLE: Lazy>, Condvar)>>> = - Lazy::new(DashMap::new); - -impl EventLoop { - pub fn new( - cpu: u32, - stack_size: usize, - min_size: usize, - max_size: usize, - keep_alive_time: u64, - ) -> std::io::Result { - let mut event_loop = EventLoop { - cpu, - #[cfg(all(target_os = "linux", feature = "io_uring"))] - operator: open_coroutine_iouring::io_uring::IoUringOperator::new(cpu)?, - selector: Poller::new()?, - waiting: AtomicBool::new(false), - pool: MaybeUninit::uninit(), - }; - let pool = CoroutinePool::new( - format!("open-coroutine-event-loop-{cpu}"), - cpu as usize, - stack_size, - min_size, - max_size, - keep_alive_time, - SelectBlocker::new(&mut event_loop), - ); - event_loop.pool = MaybeUninit::new(pool); - Ok(event_loop) - } - - pub fn submit_co( - &self, - f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + UnwindSafe + 'static, - stack_size: Option, - ) -> std::io::Result { - let coroutine = SchedulableCoroutine::new( - format!("{}|co-{}", self.get_name(), Uuid::new_v4()), - f, - stack_size.unwrap_or(self.get_stack_size()), - )?; - let co_name = Box::leak(Box::from(coroutine.get_name())); - self.submit_raw_co(coroutine)?; - Ok(CoJoinHandle::new(self, co_name)) - } - - pub fn submit( - &self, - f: impl FnOnce(&Suspender<(), ()>, Option) -> Option + UnwindSafe + 'static, - param: Option, - ) -> TaskJoinHandle { - let name = format!("{}|task-{}", self.get_name(), Uuid::new_v4()); - self.submit_raw_task(Task::new(name.clone(), f, param)); - TaskJoinHandle::new(self, &name) - } - - fn token(use_thread_id: bool) -> usize { - if let Some(co) = SchedulableCoroutine::current() { - let boxed: &'static mut CString = Box::leak(Box::from( - CString::new(co.get_name()).expect("build name failed!"), - )); - let cstr: &'static CStr = boxed.as_c_str(); - cstr.as_ptr().cast::() as usize - } else if use_thread_id { - unsafe { - cfg_if::cfg_if! { - if #[cfg(windows)] { - let thread_id = windows_sys::Win32::System::Threading::GetCurrentThread(); - } else { - let thread_id = libc::pthread_self(); - } - } - thread_id as usize - } - } else { - 0 - } - } - - pub fn add_read_event(&self, fd: c_int) -> std::io::Result<()> { - self.selector.add_read_event(fd, EventLoop::token(false)) - } - - pub fn add_write_event(&self, fd: c_int) -> std::io::Result<()> { - self.selector.add_write_event(fd, EventLoop::token(false)) - } - - pub fn del_event(&self, fd: c_int) -> std::io::Result<()> { - self.selector.del_event(fd) - } - - pub fn del_read_event(&self, fd: c_int) -> std::io::Result<()> { - self.selector.del_read_event(fd) - } - - pub fn del_write_event(&self, fd: c_int) -> std::io::Result<()> { - self.selector.del_write_event(fd) - } - - pub fn wait_event(&'static self, timeout: Option) -> std::io::Result<()> { - let left_time = if SchedulableCoroutine::current().is_some() { - timeout - } else if let Some(time) = timeout { - Some( - self.try_timed_schedule_task(time) - .map(Duration::from_nanos)?, - ) - } else { - self.try_schedule_task()?; - None - }; - self.wait_just(left_time) - } - - pub fn wait_just(&'static self, timeout: Option) -> std::io::Result<()> { - let mut timeout = timeout; - if let Some(time) = timeout { - let timestamp = get_timeout_time(time); - if let Some(co) = SchedulableCoroutine::current() { - if let CoroutineState::SystemCall((), syscall, SyscallState::Executing) = co.state() - { - let new_state = SyscallState::Suspend(timestamp); - if co.syscall((), syscall, new_state).is_err() { - crate::error!( - "{} change to syscall {} {} failed !", - co.get_name(), - syscall, - new_state - ); - } - } - } - if let Some(suspender) = SchedulableSuspender::current() { - suspender.until(timestamp); - //回来的时候等待的时间已经到了 - timeout = Some(Duration::ZERO); - } - if let Some(co) = SchedulableCoroutine::current() { - if let CoroutineState::SystemCall( - (), - syscall, - SyscallState::Callback | SyscallState::Timeout, - ) = co.state() - { - let new_state = SyscallState::Executing; - if co.syscall((), syscall, new_state).is_err() { - crate::error!( - "{} change to syscall {} {} failed !", - co.get_name(), - syscall, - new_state - ); - } - } - } - } - if self - .waiting - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_err() - { - return Ok(()); - } - #[cfg(all(target_os = "linux", feature = "io_uring"))] - if open_coroutine_iouring::version::support_io_uring() { - // use io_uring - let mut result = self.operator.select(timeout).map_err(|e| { - self.waiting.store(false, Ordering::Release); - e - })?; - for cqe in &mut result.1 { - let syscall_result = cqe.result(); - let token = cqe.user_data() as usize; - // resolve completed read/write tasks - if let Some((_, pair)) = SYSCALL_WAIT_TABLE.remove(&token) { - let (lock, cvar) = &*pair; - let mut pending = lock.lock().unwrap(); - *pending = Some(syscall_result as ssize_t); - // notify the condvar that the value has changed. - cvar.notify_one(); - } - unsafe { self.resume(token) }; - } - if result.0 > 0 && timeout.is_some() { - timeout = Some(Duration::ZERO); - } - } - - // use epoll/kevent/iocp - let mut events = Events::with_capacity(1024); - self.selector.select(&mut events, timeout).map_err(|e| { - self.waiting.store(false, Ordering::Release); - e - })?; - self.waiting.store(false, Ordering::Release); - for event in &events { - let token = event.get_token(); - if event.readable() || event.writable() { - unsafe { self.resume(token) }; - } - } - Ok(()) - } - - unsafe fn resume(&self, token: usize) { - if token == 0 { - return; - } - if let Ok(co_name) = CStr::from_ptr((token as *const c_void).cast::()).to_str() { - _ = self.try_resume(co_name); - } - } -} - -impl Deref for EventLoop { - type Target = CoroutinePool<'static>; - - fn deref(&self) -> &Self::Target { - unsafe { self.pool.assume_init_ref() } - } -} - -impl DerefMut for EventLoop { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.pool.assume_init_mut() } - } -} - -#[cfg(all(target_os = "linux", feature = "io_uring"))] -impl EventLoop { - pub fn epoll_ctl( - &self, - epfd: c_int, - op: c_int, - fd: c_int, - event: *mut epoll_event, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, epoll_ctl, epfd, op, fd, event) - } - - /// socket - pub fn socket( - &self, - domain: c_int, - ty: c_int, - protocol: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, socket, domain, ty, protocol) - } - - pub fn accept( - &self, - fd: c_int, - addr: *mut sockaddr, - len: *mut socklen_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, accept, fd, addr, len) - } - - pub fn accept4( - &self, - fd: c_int, - addr: *mut sockaddr, - len: *mut socklen_t, - flg: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, accept4, fd, addr, len, flg) - } - - pub fn connect( - &self, - socket: c_int, - address: *const sockaddr, - len: socklen_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, connect, socket, address, len) - } - - pub fn shutdown( - &self, - socket: c_int, - how: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, shutdown, socket, how) - } - - pub fn close(&self, fd: c_int) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, close, fd) - } - - /// read - pub fn recv( - &self, - socket: c_int, - buf: *mut c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, recv, socket, buf, len, flags) - } - - pub fn read( - &self, - fd: c_int, - buf: *mut c_void, - count: size_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, read, fd, buf, count) - } - - pub fn pread( - &self, - fd: c_int, - buf: *mut c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, pread, fd, buf, count, offset) - } - - pub fn readv( - &self, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, readv, fd, iov, iovcnt) - } - - pub fn preadv( - &self, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, preadv, fd, iov, iovcnt, offset) - } - - pub fn recvmsg( - &self, - fd: c_int, - msg: *mut msghdr, - flags: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, recvmsg, fd, msg, flags) - } - - /// write - - pub fn send( - &self, - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, send, socket, buf, len, flags) - } - - pub fn sendto( - &self, - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - addr: *const sockaddr, - addrlen: socklen_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!( - self.operator, - sendto, - socket, - buf, - len, - flags, - addr, - addrlen - ) - } - - pub fn write( - &self, - fd: c_int, - buf: *const c_void, - count: size_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, write, fd, buf, count) - } - - pub fn pwrite( - &self, - fd: c_int, - buf: *const c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, pwrite, fd, buf, count, offset) - } - - pub fn writev( - &self, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, writev, fd, iov, iovcnt) - } - - pub fn pwritev( - &self, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, pwritev, fd, iov, iovcnt, offset) - } - - pub fn sendmsg( - &self, - fd: c_int, - msg: *const msghdr, - flags: c_int, - ) -> std::io::Result>, Condvar)>> { - io_uring_impl!(self.operator, sendmsg, fd, msg, flags) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_simple() { - let pool = Box::leak(Box::new(EventLoop::new(0, 0, 0, 2, 0).unwrap())); - _ = pool.submit( - |_, _| { - println!("1"); - None - }, - None, - ); - _ = pool.submit( - |_, _| { - println!("2"); - None - }, - None, - ); - _ = pool.wait_event(Some(Duration::from_secs(1))); - } -} diff --git a/open-coroutine-core/src/net/event_loop/join.rs b/open-coroutine-core/src/net/event_loop/join.rs deleted file mode 100644 index 590b8598..00000000 --- a/open-coroutine-core/src/net/event_loop/join.rs +++ /dev/null @@ -1,299 +0,0 @@ -use crate::common::JoinHandler; -use crate::net::event_loop::core::EventLoop; -use std::ffi::{c_char, CStr, CString}; -use std::io::{Error, ErrorKind}; -use std::time::Duration; - -#[repr(C)] -#[derive(Debug)] -pub struct CoJoinHandle(*const EventLoop, *const c_char); - -impl JoinHandler for CoJoinHandle { - fn new(event_loop: *const EventLoop, name: &str) -> Self { - let boxed: &'static mut CString = Box::leak(Box::from( - CString::new(name).expect("init JoinHandle failed!"), - )); - let cstr: &'static CStr = boxed.as_c_str(); - CoJoinHandle(event_loop, cstr.as_ptr()) - } - - fn get_name(&self) -> std::io::Result<&str> { - unsafe { CStr::from_ptr(self.1) } - .to_str() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid task name")) - } - - fn timeout_at_join(&self, timeout_time: u64) -> std::io::Result, &str>> { - let name = self.get_name()?; - if name.is_empty() { - return Err(Error::new(ErrorKind::InvalidInput, "Invalid task name")); - } - let event_loop = unsafe { &*self.0 }; - loop { - let left_time = timeout_time - .saturating_sub(open_coroutine_timer::now()) - .min(10_000_000); - if left_time == 0 { - //timeout - return Err(Error::new(ErrorKind::TimedOut, "timeout")); - } - event_loop.wait_event(Some(Duration::from_nanos(left_time)))?; - if let Some(r) = event_loop.try_get_co_result(name) { - return Ok(r); - } - } - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct TaskJoinHandle(*const EventLoop, *const c_char); - -impl JoinHandler for TaskJoinHandle { - fn new(event_loop: *const EventLoop, name: &str) -> Self { - let boxed: &'static mut CString = Box::leak(Box::from( - CString::new(name).expect("init JoinHandle failed!"), - )); - let cstr: &'static CStr = boxed.as_c_str(); - TaskJoinHandle(event_loop, cstr.as_ptr()) - } - - fn get_name(&self) -> std::io::Result<&str> { - unsafe { CStr::from_ptr(self.1) } - .to_str() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid task name")) - } - - fn timeout_at_join(&self, timeout_time: u64) -> std::io::Result, &str>> { - let name = self.get_name()?; - if name.is_empty() { - return Err(Error::new(ErrorKind::InvalidInput, "Invalid task name")); - } - let event_loop = unsafe { &*self.0 }; - loop { - let left_time = timeout_time - .saturating_sub(open_coroutine_timer::now()) - .min(10_000_000); - if left_time == 0 { - //timeout - return Err(Error::new(ErrorKind::TimedOut, "timeout")); - } - event_loop.wait_event(Some(Duration::from_nanos(left_time)))?; - if let Some(r) = event_loop.try_get_task_result(name) { - return Ok(r); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::{Arc, Condvar, Mutex}; - - #[test] - fn co_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let event_loop = EventLoop::new(0, 0, 0, 1, 0).expect("init event loop failed!"); - let handle1 = event_loop - .submit_co( - |_, _| { - println!("[coroutine1] launched"); - Some(3) - }, - None, - ) - .unwrap(); - let handle2 = event_loop - .submit_co( - |_, _| { - println!("[coroutine2] launched"); - Some(4) - }, - None, - ) - .unwrap(); - assert_eq!(handle1.join().unwrap().unwrap(), Some(3)); - assert_eq!(handle2.join().unwrap().unwrap(), Some(4)); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::Other, "join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } - - #[test] - fn co_timed_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_timed_join".to_string()) - .spawn(move || { - let event_loop = EventLoop::new(0, 0, 0, 1, 0).expect("init event loop failed!"); - let handle = event_loop - .submit_co( - |_, _| { - println!("[coroutine3] launched"); - Some(5) - }, - None, - ) - .unwrap(); - let error = handle.timeout_join(Duration::from_nanos(0)).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::TimedOut); - assert_eq!( - handle - .timeout_join(Duration::from_secs(1)) - .unwrap() - .unwrap(), - Some(5) - ); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::Other, "timed join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } - - #[test] - fn task_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let event_loop = EventLoop::new(0, 0, 0, 1, 0).expect("init event loop failed!"); - let handle1 = event_loop.submit( - |_, _| { - println!("[task1] launched"); - Some(3) - }, - None, - ); - let handle2 = event_loop.submit( - |_, _| { - println!("[task2] launched"); - Some(4) - }, - None, - ); - assert_eq!(handle1.join().unwrap().unwrap(), Some(3)); - assert_eq!(handle2.join().unwrap().unwrap(), Some(4)); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::Other, "join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } - - #[test] - fn task_timed_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_timed_join".to_string()) - .spawn(move || { - let event_loop = EventLoop::new(0, 0, 0, 1, 0).expect("init event loop failed!"); - let handle = event_loop.submit( - |_, _| { - println!("[task3] launched"); - Some(5) - }, - None, - ); - let error = handle.timeout_join(Duration::from_nanos(0)).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::TimedOut); - assert_eq!( - handle - .timeout_join(Duration::from_secs(1)) - .unwrap() - .unwrap(), - Some(5) - ); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::Other, "timed join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } -} diff --git a/open-coroutine-core/src/net/event_loop/mod.rs b/open-coroutine-core/src/net/event_loop/mod.rs deleted file mode 100644 index e288ad48..00000000 --- a/open-coroutine-core/src/net/event_loop/mod.rs +++ /dev/null @@ -1,426 +0,0 @@ -use crate::common::Current; -use crate::coroutine::suspender::Suspender; -use crate::net::config::Config; -use crate::net::event_loop::core::EventLoop; -use crate::net::event_loop::join::{CoJoinHandle, TaskJoinHandle}; -use crate::pool::task::Task; -use crate::warn; -use core_affinity::{set_for_current, CoreId}; -use once_cell::sync::{Lazy, OnceCell}; -use std::ffi::c_int; -use std::fmt::Debug; -use std::panic::UnwindSafe; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; - -cfg_if::cfg_if! { - if #[cfg(all(target_os = "linux", feature = "io_uring"))] { - use crate::common::Named; - use crate::constants::{CoroutineState, SyscallState}; - use crate::scheduler::{SchedulableSuspender, SchedulableCoroutine}; - use libc::{c_void, epoll_event, iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; - - macro_rules! wrap_io_uring { - ( $syscall: ident, $($arg: expr),* $(,)* ) => { - EventLoops::next(false) - .$syscall($($arg, )*) - .map(|r| { - if let Some(co) = SchedulableCoroutine::current() { - if let CoroutineState::SystemCall((), syscall, SyscallState::Executing) = co.state() - { - let new_state = SyscallState::Suspend(u64::MAX); - if co.syscall((), syscall, new_state).is_err() { - crate::error!( - "{} change to syscall {} {} failed !", - co.get_name(), - syscall, - new_state - ); - } - } - } - if let Some(suspender) = SchedulableSuspender::current() { - suspender.suspend(); - //回来的时候,系统调用已经执行完了 - } - if let Some(co) = SchedulableCoroutine::current() { - if let CoroutineState::SystemCall((), syscall, SyscallState::Callback) = co.state() - { - let new_state = SyscallState::Executing; - if co.syscall((), syscall, new_state).is_err() { - crate::error!( - "{} change to syscall {} {} failed !", - co.get_name(), - syscall, - new_state - ); - } - } - } - let (lock, cvar) = &*r; - let syscall_result = cvar - .wait_while(lock.lock().unwrap(), |&mut pending| pending.is_none()) - .unwrap() - .unwrap(); - syscall_result as _ - }) - }; - } - } -} - -pub mod join; - -mod blocker; - -pub mod core; - -/// 做C兼容时会用到 -pub type UserFunc = extern "C" fn(*const Suspender<(), ()>, usize) -> usize; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct EventLoops {} - -static INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); - -static mut EVENT_LOOPS: Lazy> = Lazy::new(|| { - let config = Config::get_instance(); - warn!("open-coroutine inited with {config:#?}"); - (0..config.get_event_loop_size()) - .map(|i| { - EventLoop::new( - i as u32, - config.get_stack_size(), - config.get_min_size(), - config.get_max_size(), - config.get_keep_alive_time(), - ) - .unwrap_or_else(|_| panic!("init event-loop-{i} failed!")) - }) - .collect() -}); - -static EVENT_LOOP_WORKERS: OnceCell]>> = OnceCell::new(); - -static EVENT_LOOP_STARTED: Lazy = Lazy::new(AtomicBool::default); - -static EVENT_LOOP_START_COUNT: Lazy = Lazy::new(|| AtomicUsize::new(0)); - -static EVENT_LOOP_STOP: Lazy, Condvar)>> = - Lazy::new(|| Arc::new((Mutex::new(AtomicUsize::new(0)), Condvar::new()))); - -impl EventLoops { - fn next(skip_monitor: bool) -> &'static mut EventLoop { - unsafe { - let mut index = INDEX.fetch_add(1, Ordering::SeqCst); - if skip_monitor && index % EVENT_LOOPS.len() == 0 { - INDEX.store(1, Ordering::SeqCst); - EVENT_LOOPS.get_mut(1).expect("init event-loop-1 failed!") - } else { - index %= EVENT_LOOPS.len(); - EVENT_LOOPS - .get_mut(index) - .unwrap_or_else(|| panic!("init event-loop-{index} failed!")) - } - } - } - - pub(crate) fn monitor() -> &'static EventLoop { - //monitor线程的EventLoop固定 - unsafe { - EVENT_LOOPS - .first() - .expect("init event-loop-monitor failed!") - } - } - - pub(crate) fn new_condition() -> Arc<(Mutex, Condvar)> { - Arc::clone(&EVENT_LOOP_STOP) - } - - fn start() { - if EVENT_LOOP_STARTED - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - //初始化event_loop线程 - _ = EVENT_LOOP_WORKERS.get_or_init(|| { - (1..unsafe { EVENT_LOOPS.len() }) - .map(|i| { - std::thread::Builder::new() - .name(format!("open-coroutine-event-loop-{i}")) - .spawn(move || { - warn!("open-coroutine-event-loop-{i} has started"); - _ = EVENT_LOOP_START_COUNT.fetch_add(1, Ordering::Release); - if set_for_current(CoreId { id: i }) { - warn!("pin event-loop-{i} thread to CPU core-{i} failed !"); - } - let event_loop = Self::next(true); - EventLoop::init_current(event_loop); - while EVENT_LOOP_STARTED.load(Ordering::Acquire) - || event_loop.has_task() - { - _ = event_loop.wait_event(Some(Duration::from_millis(10))); - } - EventLoop::clean_current(); - let pair = Self::new_condition(); - let (lock, cvar) = pair.as_ref(); - let pending = lock.lock().unwrap(); - _ = pending.fetch_add(1, Ordering::Release); - cvar.notify_one(); - warn!("open-coroutine-event-loop-{i} has exited"); - }) - .expect("failed to spawn event-loop thread") - }) - .collect() - }); - } - } - - pub fn stop() { - warn!("open-coroutine is exiting..."); - if EVENT_LOOP_STARTED - .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - // wait for the event-loops to stop - let (lock, cvar) = EVENT_LOOP_STOP.as_ref(); - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(30000), - |stopped| { - stopped.load(Ordering::Acquire) - < EVENT_LOOP_START_COUNT.load(Ordering::Acquire) - 1 - }, - ) - .unwrap() - .1; - if result.timed_out() { - crate::error!("open-coroutine didn't exit successfully within 30 seconds !"); - } - } - #[cfg(all(unix, feature = "preemptive-schedule"))] - crate::monitor::Monitor::stop(); - crate::info!("open-coroutine exit successfully !"); - } - - pub fn submit_co( - f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + UnwindSafe + 'static, - stack_size: Option, - ) -> std::io::Result { - Self::start(); - Self::next(true).submit_co(f, stack_size) - } - - pub fn submit( - f: impl FnOnce(&Suspender<(), ()>, Option) -> Option + UnwindSafe + 'static, - param: Option, - ) -> TaskJoinHandle { - Self::start(); - Self::next(true).submit(f, param) - } - - pub(crate) fn submit_raw(task: Task<'static>) { - Self::next(true).submit_raw_task(task); - } - - fn slice_wait_just( - timeout: Option, - event_loop: &'static EventLoop, - ) -> std::io::Result<()> { - let timeout_time = timeout.map_or(u64::MAX, open_coroutine_timer::get_timeout_time); - loop { - let left_time = timeout_time - .saturating_sub(open_coroutine_timer::now()) - .min(10_000_000); - if left_time == 0 { - //timeout - return event_loop.wait_just(Some(Duration::ZERO)); - } - event_loop.wait_just(Some(Duration::from_nanos(left_time)))?; - } - } - - pub fn wait_just(timeout: Option) -> std::io::Result<()> { - Self::slice_wait_just(timeout, Self::next(true)) - } - - pub fn wait_read_event(fd: c_int, timeout: Option) -> std::io::Result<()> { - let event_loop = Self::next(false); - event_loop.add_read_event(fd)?; - if Self::monitor().eq(event_loop) { - // wait only happens in non-monitor for non-monitor thread - return Self::wait_just(timeout); - } - Self::slice_wait_just(timeout, event_loop) - } - - pub fn wait_write_event(fd: c_int, timeout: Option) -> std::io::Result<()> { - let event_loop = Self::next(false); - event_loop.add_write_event(fd)?; - if Self::monitor().eq(event_loop) { - // wait only happens in non-monitor for non-monitor thread - return Self::wait_just(timeout); - } - Self::slice_wait_just(timeout, event_loop) - } - - pub fn del_event(fd: c_int) { - (0..unsafe { EVENT_LOOPS.len() }).for_each(|_| { - _ = Self::next(false).del_event(fd); - }); - } - - pub fn del_read_event(fd: c_int) { - (0..unsafe { EVENT_LOOPS.len() }).for_each(|_| { - _ = Self::next(false).del_read_event(fd); - }); - } - - pub fn del_write_event(fd: c_int) { - (0..unsafe { EVENT_LOOPS.len() }).for_each(|_| { - _ = Self::next(false).del_write_event(fd); - }); - } -} - -#[allow(unused_variables, clippy::not_unsafe_ptr_arg_deref)] -#[cfg(all(target_os = "linux", feature = "io_uring"))] -impl EventLoops { - pub fn epoll_ctl( - epfd: c_int, - op: c_int, - fd: c_int, - event: *mut epoll_event, - ) -> std::io::Result { - wrap_io_uring!(epoll_ctl, epfd, op, fd, event) - } - - /// socket - pub fn socket(domain: c_int, ty: c_int, protocol: c_int) -> std::io::Result { - wrap_io_uring!(socket, domain, ty, protocol) - } - - pub fn accept(fd: c_int, addr: *mut sockaddr, len: *mut socklen_t) -> std::io::Result { - wrap_io_uring!(accept, fd, addr, len) - } - - pub fn accept4( - fd: c_int, - addr: *mut sockaddr, - len: *mut socklen_t, - flg: c_int, - ) -> std::io::Result { - wrap_io_uring!(accept4, fd, addr, len, flg) - } - - pub fn connect( - socket: c_int, - address: *const sockaddr, - len: socklen_t, - ) -> std::io::Result { - wrap_io_uring!(connect, socket, address, len) - } - - pub fn shutdown(socket: c_int, how: c_int) -> std::io::Result { - wrap_io_uring!(shutdown, socket, how) - } - - pub fn close(fd: c_int) -> std::io::Result { - wrap_io_uring!(close, fd) - } - - /// read - pub fn recv( - socket: c_int, - buf: *mut c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result { - wrap_io_uring!(recv, socket, buf, len, flags) - } - - pub fn read(fd: c_int, buf: *mut c_void, count: size_t) -> std::io::Result { - wrap_io_uring!(read, fd, buf, count) - } - - pub fn pread( - fd: c_int, - buf: *mut c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result { - wrap_io_uring!(pread, fd, buf, count, offset) - } - - pub fn readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> std::io::Result { - wrap_io_uring!(readv, fd, iov, iovcnt) - } - - pub fn preadv( - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result { - wrap_io_uring!(preadv, fd, iov, iovcnt, offset) - } - - pub fn recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> std::io::Result { - wrap_io_uring!(recvmsg, fd, msg, flags) - } - - /// write - pub fn send( - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result { - wrap_io_uring!(send, socket, buf, len, flags) - } - - pub fn sendto( - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - addr: *const sockaddr, - addrlen: socklen_t, - ) -> std::io::Result { - wrap_io_uring!(sendto, socket, buf, len, flags, addr, addrlen) - } - - pub fn write(fd: c_int, buf: *const c_void, count: size_t) -> std::io::Result { - wrap_io_uring!(write, fd, buf, count) - } - - pub fn pwrite( - fd: c_int, - buf: *const c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result { - wrap_io_uring!(pwrite, fd, buf, count, offset) - } - - pub fn writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> std::io::Result { - wrap_io_uring!(writev, fd, iov, iovcnt) - } - - pub fn pwritev( - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result { - wrap_io_uring!(pwritev, fd, iov, iovcnt, offset) - } - - pub fn sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> std::io::Result { - wrap_io_uring!(sendmsg, fd, msg, flags) - } -} diff --git a/open-coroutine-core/src/net/mod.rs b/open-coroutine-core/src/net/mod.rs deleted file mode 100644 index 4d4067b8..00000000 --- a/open-coroutine-core/src/net/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod config; - -#[allow( - dead_code, - clippy::cast_possible_wrap, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - trivial_numeric_casts -)] -pub mod event_loop; - -mod selector; diff --git a/open-coroutine-core/src/pool/join.rs b/open-coroutine-core/src/pool/join.rs deleted file mode 100644 index 7c48db5f..00000000 --- a/open-coroutine-core/src/pool/join.rs +++ /dev/null @@ -1,151 +0,0 @@ -use crate::common::JoinHandler; -use crate::pool::CoroutinePool; -use std::ffi::{c_char, CStr, CString}; -use std::io::{Error, ErrorKind}; -use std::time::Duration; - -#[allow(missing_docs)] -#[repr(C)] -#[derive(Debug)] -pub struct JoinHandle<'j>(*const CoroutinePool<'j>, *const c_char); - -impl<'j> JoinHandler> for JoinHandle<'j> { - fn new(pool: *const CoroutinePool<'j>, name: &str) -> Self { - let boxed: &'static mut CString = Box::leak(Box::from( - CString::new(name).expect("init JoinHandle failed!"), - )); - let cstr: &'static CStr = boxed.as_c_str(); - JoinHandle(pool, cstr.as_ptr()) - } - - fn get_name(&self) -> std::io::Result<&str> { - unsafe { CStr::from_ptr(self.1) } - .to_str() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid task name")) - } - - fn timeout_at_join(&self, timeout_time: u64) -> std::io::Result, &str>> { - let name = self.get_name()?; - if name.is_empty() { - return Err(Error::new(ErrorKind::InvalidInput, "Invalid task name")); - } - let pool = unsafe { &*self.0 }; - pool.wait_result( - name, - Duration::from_nanos(timeout_time.saturating_sub(open_coroutine_timer::now())), - ) - .map(|r| r.expect("result is None !")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::{Arc, Condvar, Mutex}; - - #[test] - fn join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let pool = CoroutinePool::default(); - let handle1 = pool.submit( - None, - |_, _| { - println!("[coroutine1] launched"); - Some(3) - }, - None, - ); - let handle2 = pool.submit( - None, - |_, _| { - println!("[coroutine2] launched"); - Some(4) - }, - None, - ); - pool.try_schedule_task().unwrap(); - assert_eq!(handle1.join().unwrap().unwrap().unwrap(), 3); - assert_eq!(handle2.join().unwrap().unwrap().unwrap(), 4); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::TimedOut, "join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } - - #[test] - fn timed_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_timed_join".to_string()) - .spawn(move || { - let pool = CoroutinePool::default(); - let handle = pool.submit( - None, - |_, _| { - println!("[coroutine3] launched"); - Some(5) - }, - None, - ); - let error = handle.timeout_join(Duration::from_nanos(0)).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::TimedOut); - pool.try_schedule_task().unwrap(); - assert_eq!( - handle - .timeout_join(Duration::from_secs(1)) - .unwrap() - .unwrap() - .unwrap(), - 5 - ); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::TimedOut, "timed join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } -} diff --git a/open-coroutine-core/src/pool/mod.rs b/open-coroutine-core/src/pool/mod.rs deleted file mode 100644 index bc3f5fc5..00000000 --- a/open-coroutine-core/src/pool/mod.rs +++ /dev/null @@ -1,430 +0,0 @@ -use crate::common::{Blocker, Current, JoinHandler, Named, Pool, StatePool}; -use crate::constants::PoolState; -use crate::coroutine::suspender::Suspender; -use crate::pool::creator::CoroutineCreator; -use crate::pool::join::JoinHandle; -use crate::pool::task::Task; -use crate::scheduler::{SchedulableSuspender, Scheduler}; -use crate::{error, impl_current_for, impl_for_named}; -use crossbeam_deque::{Injector, Steal}; -use dashmap::DashMap; -use std::cell::{Cell, RefCell}; -use std::fmt::Debug; -use std::io::{Error, ErrorKind}; -use std::ops::{Deref, DerefMut}; -use std::panic::{RefUnwindSafe, UnwindSafe}; -use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; -use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; -use uuid::Uuid; - -/// Task abstraction and impl. -pub mod task; - -/// Task join abstraction and impl. -pub mod join; - -mod creator; - -#[cfg(test)] -mod tests; - -#[repr(C)] -#[derive(Debug)] -pub struct CoroutinePool<'p> { - //绑定到哪个CPU核心 - cpu: usize, - //协程池状态 - state: Cell, - //任务队列 - task_queue: Injector>, - //工作协程组 - workers: Scheduler<'p>, - //是否正在调度,不允许多线程并行调度 - scheduling: AtomicBool, - //当前协程数 - running: AtomicUsize, - //尝试取出任务失败的次数 - pop_fail_times: AtomicUsize, - //最小协程数,即核心协程数 - min_size: AtomicUsize, - //最大协程数 - max_size: AtomicUsize, - //非核心协程的最大存活时间,单位ns - keep_alive_time: AtomicU64, - //阻滞器 - blocker: RefCell>, - //任务执行结果 - results: DashMap, &'p str>>, - //正在等待结果的 - waits: DashMap<&'p str, Arc<(Mutex, Condvar)>>, -} - -impl<'p> CoroutinePool<'p> { - /// Create a new `CoroutinePool` instance. - pub fn new( - name: String, - cpu: usize, - stack_size: usize, - min_size: usize, - max_size: usize, - keep_alive_time: u64, - blocker: impl Blocker + 'p, - ) -> Self { - let mut workers = Scheduler::new(name, stack_size); - workers.add_listener(CoroutineCreator::default()); - CoroutinePool { - cpu, - state: Cell::new(PoolState::Created), - workers, - scheduling: AtomicBool::new(false), - running: AtomicUsize::new(0), - pop_fail_times: AtomicUsize::new(0), - min_size: AtomicUsize::new(min_size), - max_size: AtomicUsize::new(max_size), - task_queue: Injector::default(), - keep_alive_time: AtomicU64::new(keep_alive_time), - blocker: RefCell::new(Box::new(blocker)), - results: DashMap::new(), - waits: DashMap::default(), - } - } - - /// Create a coroutine in this pool. - /// - /// # Errors - /// if create failed. - fn try_grow(&self) -> std::io::Result<()> { - if !self.has_task() || self.get_running_size() >= self.get_max_size() { - return Ok(()); - } - let create_time = open_coroutine_timer::now(); - self.submit_co( - move |suspender, ()| { - loop { - if let Some(pool) = Self::current() { - if pool.try_run(suspender).is_some() { - pool.pop_fail_times.store(0, Ordering::Release); - continue; - } - let recycle = match pool.state() { - PoolState::Created | PoolState::Running(_) => false, - PoolState::Stopping(_) | PoolState::Stopped => true, - }; - let running = pool.get_running_size(); - if open_coroutine_timer::now().saturating_sub(create_time) - >= pool.get_keep_alive_time() - && running > pool.get_min_size() - || recycle - { - return None; - } - _ = pool.pop_fail_times.fetch_add(1, Ordering::Release); - match pool.pop_fail_times.load(Ordering::Acquire).cmp(&running) { - //让出CPU给下一个协程 - std::cmp::Ordering::Less => suspender.suspend(), - //减少CPU在N个无任务的协程中空轮询 - std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => { - loop { - if let Ok(blocker) = pool.blocker.try_borrow() { - blocker.block(Duration::from_millis(1)); - break; - } - } - pool.pop_fail_times.store(0, Ordering::Release); - } - } - } else { - error!("current pool not found"); - return None; - } - } - }, - None, - ) - .map(|_| { - _ = self.running.fetch_add(1, Ordering::Release); - }) - } - - /// Attempt to run a task in current coroutine or thread. - fn try_run(&self, suspender: &Suspender<(), ()>) -> Option<()> { - self.pop().map(|task| { - let (task_name, result) = task.run(suspender); - assert!( - self.results.insert(task_name.clone(), result).is_none(), - "The previous result was not retrieved in a timely manner" - ); - if let Some(arc) = self.waits.get(&*task_name) { - let (lock, cvar) = &**arc; - let mut pending = lock.lock().unwrap(); - *pending = false; - // Notify the condvar that the value has changed. - cvar.notify_one(); - } - }) - } - - /// pop a task - pub(crate) fn pop(&self) -> Option> { - // Fast path, if len == 0, then there are no values - if !self.has_task() { - return None; - } - loop { - match self.task_queue.steal() { - Steal::Success(item) => return Some(item), - Steal::Retry => continue, - Steal::Empty => return None, - } - } - } - - /// Returns `true` if the task queue is empty. - pub fn has_task(&self) -> bool { - self.count() != 0 - } - - /// Returns the number of tasks owned by this pool. - pub fn count(&self) -> usize { - self.task_queue.len() - } - - /// Change the blocker in this pool. - pub fn change_blocker(&self, blocker: impl Blocker + 'p) -> Box { - self.blocker.replace(Box::new(blocker)) - } - - /// Submit a new task to this pool. - /// - /// Allow multiple threads to concurrently submit task to the pool, - /// but only allow one thread to execute scheduling. - pub fn submit( - &self, - name: Option, - func: impl FnOnce(&Suspender<(), ()>, Option) -> Option + UnwindSafe + 'p, - param: Option, - ) -> JoinHandle<'p> { - let name = name.unwrap_or(format!("{}|task-{}", self.get_name(), Uuid::new_v4())); - self.submit_raw_task(Task::new(name.clone(), func, param)); - JoinHandle::new(self, &name) - } - - /// Submit new task to this pool. - /// - /// Allow multiple threads to concurrently submit task to the pool, - /// but only allow one thread to execute scheduling. - pub fn submit_raw_task(&self, task: Task<'p>) { - self.task_queue.push(task); - } - - /// Schedule the tasks. - /// - /// Allow multiple threads to concurrently submit task to the pool, - /// but only allow one thread to execute scheduling. - /// - /// # Errors - /// see `try_timeout_schedule`. - pub fn try_schedule_task(&self) -> std::io::Result<()> { - self.try_timeout_schedule_task(Duration::MAX.as_secs()) - .map(|_| ()) - } - - /// Try scheduling the tasks for up to `dur`. - /// - /// Allow multiple threads to concurrently submit task to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// # Errors - /// see `try_timeout_schedule`. - pub fn try_timed_schedule_task(&self, dur: Duration) -> std::io::Result { - self.try_timeout_schedule_task(open_coroutine_timer::get_timeout_time(dur)) - } - - /// Attempt to schedule the tasks before the `timeout_time` timestamp. - /// - /// Allow multiple threads to concurrently submit task to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// Returns the left time in ns. - /// - /// # Errors - /// if change to ready fails. - pub fn try_timeout_schedule_task(&self, timeout_time: u64) -> std::io::Result { - if self - .scheduling - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_err() - { - return Ok(timeout_time.saturating_sub(open_coroutine_timer::now())); - } - self.running(true)?; - Self::init_current(self); - match self.state() { - PoolState::Created | PoolState::Running(_) | PoolState::Stopping(_) => { - self.try_grow()?; - } - PoolState::Stopped => { - return Err(Error::new( - ErrorKind::Other, - "The coroutine pool is stopped !", - )) - } - }; - let result = self.try_timeout_schedule(timeout_time); - Self::clean_current(); - self.scheduling.store(false, Ordering::Release); - result - } - - /// Submit a new task to this pool and wait for the task to complete. - /// - /// # Errors - /// see `wait_result` - pub fn submit_and_wait( - &self, - name: Option, - func: impl FnOnce(&Suspender<(), ()>, Option) -> Option + UnwindSafe + 'p, - param: Option, - wait_time: Duration, - ) -> std::io::Result, &str>>> { - let join = self.submit(name, func, param); - self.wait_result(join.get_name()?, wait_time) - } - - /// Attempt to obtain task results with the given `task_name`. - pub fn try_get_task_result(&self, task_name: &str) -> Option, &'p str>> { - self.results.remove(task_name).map(|r| r.1) - } - - /// Use the given `task_name` to obtain task results, and if no results are found, - /// block the current thread for `wait_time`. - /// - /// # Errors - /// if timeout - pub fn wait_result( - &self, - task_name: &str, - wait_time: Duration, - ) -> std::io::Result, &str>>> { - let key = Box::leak(Box::from(task_name)); - if let Some(r) = self.try_get_task_result(key) { - _ = self.waits.remove(key); - return Ok(Some(r)); - } - if let Some(suspender) = SchedulableSuspender::current() { - let timeout_time = open_coroutine_timer::get_timeout_time(wait_time); - loop { - _ = self.try_run(suspender); - if let Some(r) = self.try_get_task_result(key) { - return Ok(Some(r)); - } - if timeout_time.saturating_sub(open_coroutine_timer::now()) == 0 { - return Err(Error::new(ErrorKind::TimedOut, "wait timeout")); - } - } - } - let arc = if let Some(arc) = self.waits.get(key) { - arc.clone() - } else { - let arc = Arc::new((Mutex::new(true), Condvar::new())); - assert!(self.waits.insert(key, arc.clone()).is_none()); - arc - }; - let (lock, cvar) = &*arc; - _ = cvar - .wait_timeout_while(lock.lock().unwrap(), wait_time, |&mut pending| pending) - .unwrap(); - if let Some(r) = self.try_get_task_result(key) { - assert!(self.waits.remove(key).is_some()); - return Ok(Some(r)); - } - Err(Error::new(ErrorKind::TimedOut, "wait timeout")) - } -} - -impl RefUnwindSafe for CoroutinePool<'_> {} - -impl Drop for CoroutinePool<'_> { - fn drop(&mut self) { - if !std::thread::panicking() { - assert!( - !self.has_task(), - "there are still tasks to be carried out !" - ); - } - } -} - -impl_for_named!(CoroutinePool<'p>); - -impl_current_for!(COROUTINE_POOL, CoroutinePool<'p>); - -impl Default for CoroutinePool<'_> { - fn default() -> Self { - Self::new( - format!("open-coroutine-pool-{:?}", std::thread::current().id()), - 1, - crate::constants::DEFAULT_STACK_SIZE, - 0, - 65536, - 0, - crate::common::DelayBlocker::default(), - ) - } -} - -impl<'p> Deref for CoroutinePool<'p> { - type Target = Scheduler<'p>; - - fn deref(&self) -> &Self::Target { - &self.workers - } -} - -impl<'p> DerefMut for CoroutinePool<'p> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.workers - } -} - -impl Pool for CoroutinePool<'_> { - fn set_min_size(&self, min_size: usize) { - self.min_size.store(min_size, Ordering::Release); - } - - fn get_min_size(&self) -> usize { - self.min_size.load(Ordering::Acquire) - } - - fn get_running_size(&self) -> usize { - self.running.load(Ordering::Acquire) - } - - fn set_max_size(&self, max_size: usize) { - self.max_size.store(max_size, Ordering::Release); - } - - fn get_max_size(&self) -> usize { - self.max_size.load(Ordering::Acquire) - } - - fn set_keep_alive_time(&self, keep_alive_time: u64) { - self.keep_alive_time - .store(keep_alive_time, Ordering::Release); - } - - fn get_keep_alive_time(&self) -> u64 { - self.keep_alive_time.load(Ordering::Acquire) - } -} - -impl StatePool for CoroutinePool<'_> { - fn state(&self) -> PoolState { - self.state.get() - } - - fn change_state(&self, state: PoolState) -> PoolState { - self.state.replace(state) - } -} diff --git a/open-coroutine-core/src/pool/task.rs b/open-coroutine-core/src/pool/task.rs deleted file mode 100644 index 896329ba..00000000 --- a/open-coroutine-core/src/pool/task.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::catch; -use crate::common::Named; -use crate::coroutine::suspender::Suspender; -use std::cell::Cell; -use std::fmt::{Debug, Formatter}; -use std::panic::UnwindSafe; - -/// Note: the param and the result is raw pointer. -#[repr(C)] -#[allow(clippy::type_complexity)] -pub struct Task<'t> { - name: String, - func: Box, Option) -> Option + UnwindSafe + 't>, - param: Cell>, -} - -impl Debug for Task<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Task") - .field("name", &self.name) - .field("param", &self.param) - .finish_non_exhaustive() - } -} - -impl Named for Task<'_> { - fn get_name(&self) -> &str { - &self.name - } -} - -impl<'t> Task<'t> { - pub fn new( - name: String, - func: impl FnOnce(&Suspender<(), ()>, Option) -> Option + UnwindSafe + 't, - param: Option, - ) -> Self { - Task { - name, - func: Box::new(func), - param: Cell::new(param), - } - } - - /// Set a param for this task. - pub fn set_param(&self, param: usize) -> Option { - self.param.replace(Some(param)) - } - - /// Get param from this task. - pub fn get_param(&self) -> Option { - self.param.get() - } - - /// exec the task - /// - /// # Errors - /// if an exception occurred while executing this task. - pub fn run<'e>( - self, - suspender: &Suspender<(), ()>, - ) -> (String, Result, &'e str>) { - let paran = self.get_param(); - ( - self.name.clone(), - catch!( - || (self.func)(suspender, paran), - "task failed without message", - "task", - self.name - ), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::co; - use crate::constants::CoroutineState; - - #[test] - fn test() -> std::io::Result<()> { - let mut coroutine = co!(|suspender, _| { - let task = Task::new( - String::from("test"), - |_, p| { - println!("hello"); - p - }, - None, - ); - assert_eq!((String::from("test"), Ok(None)), task.run(suspender)); - }); - assert_eq!(CoroutineState::Complete(()), coroutine.resume()?); - Ok(()) - } - - #[test] - fn test_panic() -> std::io::Result<()> { - let mut coroutine = co!(|suspender, _| { - let task = Task::new( - String::from("test"), - |_, _| panic!("test panic, just ignore it"), - None, - ); - assert_eq!( - (String::from("test"), Err("test panic, just ignore it")), - task.run(suspender) - ); - }); - assert_eq!(CoroutineState::Complete(()), coroutine.resume()?); - Ok(()) - } -} diff --git a/open-coroutine-core/src/pool/tests.rs b/open-coroutine-core/src/pool/tests.rs deleted file mode 100644 index 5a843254..00000000 --- a/open-coroutine-core/src/pool/tests.rs +++ /dev/null @@ -1,135 +0,0 @@ -use super::*; - -#[test] -fn test_simple() { - let task_name = "test_simple"; - let pool = CoroutinePool::default(); - pool.set_max_size(1); - assert!(!pool.has_task()); - _ = pool.submit( - Some(String::from("test_panic")), - |_, _| panic!("test panic, just ignore it"), - None, - ); - assert!(pool.has_task()); - let name = pool.submit( - Some(String::from(task_name)), - |_, _| { - println!("2"); - Some(2) - }, - None, - ); - assert_eq!(task_name, name.get_name().unwrap()); - _ = pool.try_schedule_task(); - assert_eq!( - Some(Err("test panic, just ignore it")), - pool.try_get_task_result("test_panic") - ); - assert_eq!(Some(Ok(Some(2))), pool.try_get_task_result(task_name)); -} - -#[test] -fn test_suspend() -> std::io::Result<()> { - let pool = CoroutinePool::default(); - pool.set_max_size(2); - _ = pool.submit( - None, - |_, param| { - println!("[coroutine] delay"); - if let Some(suspender) = SchedulableSuspender::current() { - suspender.delay(Duration::from_millis(100)); - } - println!("[coroutine] back"); - param - }, - None, - ); - _ = pool.submit( - None, - |_, _| { - println!("middle"); - Some(1) - }, - None, - ); - pool.try_schedule_task()?; - std::thread::sleep(Duration::from_millis(200)); - pool.try_schedule_task() -} - -#[test] -fn test_wait() { - let task_name = "test_wait"; - let pool = CoroutinePool::default(); - pool.set_max_size(1); - assert!(!pool.has_task()); - let name = pool.submit( - Some(String::from(task_name)), - |_, _| { - println!("2"); - Some(2) - }, - None, - ); - assert_eq!(task_name, name.get_name().unwrap()); - assert_eq!(None, pool.try_get_task_result(task_name)); - match pool.wait_result(task_name, Duration::from_millis(100)) { - Ok(_) => panic!(), - Err(_) => {} - } - assert_eq!(None, pool.try_get_task_result(task_name)); - _ = pool.try_schedule_task(); - match pool.wait_result(task_name, Duration::from_secs(100)) { - Ok(v) => assert_eq!(Some(Ok(Some(2))), v), - Err(e) => panic!("{e}"), - } -} - -#[test] -fn test_co_simple() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - let task_name = "test_co_simple"; - let pool = CoroutinePool::default(); - pool.set_max_size(1); - let result = pool.submit_and_wait( - Some(String::from(task_name)), - |_, _| Some(1), - None, - Duration::from_secs(1), - ); - assert_eq!(Some(Ok(Some(1))), result.unwrap()); - None - }, - None, - )?; - scheduler.try_schedule() -} - -#[test] -fn test_nest() { - let pool = Arc::new(CoroutinePool::default()); - pool.set_max_size(1); - let arc = pool.clone(); - _ = pool.submit_and_wait( - None, - move |_, _| { - println!("start"); - _ = arc.submit_and_wait( - None, - |_, _| { - println!("middle"); - None - }, - None, - Duration::from_secs(1), - ); - println!("end"); - None - }, - None, - Duration::from_secs(1), - ); -} diff --git a/open-coroutine-core/src/scheduler/join.rs b/open-coroutine-core/src/scheduler/join.rs deleted file mode 100644 index 674e4846..00000000 --- a/open-coroutine-core/src/scheduler/join.rs +++ /dev/null @@ -1,158 +0,0 @@ -use crate::common::JoinHandler; -use crate::scheduler::Scheduler; -use std::ffi::{c_char, CStr, CString}; -use std::io::{Error, ErrorKind}; - -#[allow(missing_docs)] -#[repr(C)] -#[derive(Debug)] -pub struct JoinHandle<'j>(*const Scheduler<'j>, *const c_char); - -impl<'j> JoinHandler> for JoinHandle<'j> { - fn new(pool: *const Scheduler<'j>, name: &str) -> Self { - let boxed: &'static mut CString = Box::leak(Box::from( - CString::new(name).expect("init JoinHandle failed!"), - )); - let cstr: &'static CStr = boxed.as_c_str(); - JoinHandle(pool, cstr.as_ptr()) - } - - fn get_name(&self) -> std::io::Result<&str> { - unsafe { CStr::from_ptr(self.1) } - .to_str() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Invalid coroutine name")) - } - - fn timeout_at_join(&self, timeout_time: u64) -> std::io::Result, &str>> { - let name = self.get_name()?; - if name.is_empty() { - return Err(Error::new( - ErrorKind::InvalidInput, - "Invalid coroutine name", - )); - } - let scheduler = unsafe { &*self.0 }; - loop { - if 0 == scheduler.try_timeout_schedule(timeout_time)? { - return Err(Error::new(ErrorKind::TimedOut, "timeout")); - } - if let Some(r) = scheduler.try_get_co_result(name) { - return Ok(r); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::sync::{Arc, Condvar, Mutex}; - use std::time::Duration; - - #[test] - fn join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let pool = Scheduler::default(); - let handle1 = pool - .submit_co( - |_, _| { - println!("[coroutine1] launched"); - Some(3) - }, - None, - ) - .unwrap(); - let handle2 = pool - .submit_co( - |_, _| { - println!("[coroutine2] launched"); - Some(4) - }, - None, - ) - .unwrap(); - assert_eq!(handle1.join().unwrap().unwrap().unwrap(), 3); - assert_eq!(handle2.join().unwrap().unwrap().unwrap(), 4); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::TimedOut, "join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } - - #[test] - fn timed_join_test() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_timed_join".to_string()) - .spawn(move || { - let pool = Scheduler::default(); - let handle = pool - .submit_co( - |_, _| { - println!("[coroutine3] launched"); - Some(5) - }, - None, - ) - .unwrap(); - let error = handle.timeout_join(Duration::from_nanos(0)).unwrap_err(); - assert_eq!(error.kind(), ErrorKind::TimedOut); - assert_eq!( - handle - .timeout_join(Duration::from_secs(1)) - .unwrap() - .unwrap() - .unwrap(), - 5 - ); - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(Error::new(ErrorKind::TimedOut, "timed join failed")) - } else { - handler.join().unwrap(); - Ok(()) - } - } -} diff --git a/open-coroutine-core/src/scheduler/mod.rs b/open-coroutine-core/src/scheduler/mod.rs deleted file mode 100644 index c9b94b21..00000000 --- a/open-coroutine-core/src/scheduler/mod.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::common::{Current, JoinHandler, Named}; -use crate::constants::{CoroutineState, SyscallState}; -use crate::coroutine::listener::Listener; -use crate::coroutine::suspender::Suspender; -use crate::coroutine::Coroutine; -use crate::scheduler::join::JoinHandle; -use crate::{error, impl_current_for, impl_for_named}; -use dashmap::DashMap; -use once_cell::sync::Lazy; -use open_coroutine_queue::LocalQueue; -use open_coroutine_timer::TimerList; -use std::collections::{HashMap, VecDeque}; -use std::fmt::Debug; -use std::ops::Deref; -use std::panic::UnwindSafe; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use uuid::Uuid; - -/// A type for Scheduler. -pub type SchedulableCoroutineState = CoroutineState<(), Option>; - -/// A type for Scheduler. -pub type SchedulableCoroutine<'s> = Coroutine<'s, (), (), Option>; - -/// A type for Scheduler. -pub type SchedulableSuspender<'s> = Suspender<'s, (), ()>; - -/// A type for Scheduler. -pub trait SchedulableListener: Listener<(), (), Option> {} - -/// Join impl for scheduler. -pub mod join; - -#[cfg(test)] -mod tests; - -static mut SUSPEND_TABLE: Lazy> = Lazy::new(TimerList::default); - -static mut SYSTEM_CALL_TABLE: Lazy> = Lazy::new(HashMap::new); - -static mut SYSTEM_CALL_SUSPEND_TABLE: Lazy> = Lazy::new(TimerList::default); - -#[repr(C)] -#[derive(Debug)] -pub struct Scheduler<'s> { - name: String, - scheduling: AtomicBool, - stack_size: AtomicUsize, - ready: LocalQueue<'s, SchedulableCoroutine<'static>>, - results: DashMap<&'s str, Result, &'s str>>, - listeners: VecDeque<&'s dyn SchedulableListener>, -} - -impl<'s> Scheduler<'s> { - #[must_use] - pub fn new(name: String, stack_size: usize) -> Self { - Scheduler { - name, - scheduling: AtomicBool::new(false), - stack_size: AtomicUsize::new(stack_size), - ready: LocalQueue::default(), - results: DashMap::default(), - listeners: VecDeque::default(), - } - } - - /// Get the default stack size for the coroutines in this scheduler. - /// If it has not been set, it will be `crate::constant::DEFAULT_STACK_SIZE`. - pub fn get_stack_size(&self) -> usize { - self.stack_size.load(Ordering::Acquire) - } - - /// Set the default stack size for the coroutines in this scheduler. - /// If it has not been set, it will be `crate::constant::DEFAULT_STACK_SIZE`. - pub fn set_stack_size(&self, stack_size: usize) { - self.stack_size.store(stack_size, Ordering::Release); - } - - /// Submit a closure to create new coroutine, then the coroutine will be push into ready queue. - /// - /// Allow multiple threads to concurrently submit coroutine to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// # Errors - /// if create coroutine fails. - pub fn submit_co( - &self, - f: impl FnOnce(&Suspender<(), ()>, ()) -> Option + UnwindSafe + 'static, - stack_size: Option, - ) -> std::io::Result> { - let coroutine = SchedulableCoroutine::new( - format!("{}|co-{}", self.get_name(), Uuid::new_v4()), - f, - stack_size.unwrap_or(self.get_stack_size()), - )?; - let co_name = Box::leak(Box::from(coroutine.get_name())); - self.submit_raw_co(coroutine)?; - Ok(JoinHandle::new(self, co_name)) - } - - /// Submit a closure to create new coroutine, then the coroutine will be push into ready queue. - /// - /// Allow multiple threads to concurrently submit coroutine to the scheduler, - /// but only allow one thread to execute scheduling. - pub fn submit_raw_co( - &self, - mut coroutine: SchedulableCoroutine<'static>, - ) -> std::io::Result<()> { - for listener in &self.listeners { - #[allow(clippy::transmute_ptr_to_ptr, clippy::missing_transmute_annotations)] - coroutine.add_raw_listener(unsafe { std::mem::transmute(*listener) }); - } - coroutine.ready()?; - self.ready.push_back(coroutine); - Ok(()) - } - - /// Resume a coroutine from the system call table to the ready queue, - /// it's generally only required for framework level crates. - /// - /// If we can't find the coroutine, nothing happens. - /// - /// # Errors - /// if change to ready fails. - pub fn try_resume(&self, co_name: &str) -> std::io::Result<()> { - let co_name: &str = Box::leak(Box::from(co_name)); - unsafe { - if let Some(co) = SYSTEM_CALL_TABLE.remove(co_name) { - let state = co.state(); - match state { - CoroutineState::SystemCall(val, syscall, SyscallState::Suspend(_)) => { - co.syscall(val, syscall, SyscallState::Callback) - .expect("change syscall state failed"); - } - _ => panic!("try_resume should never execute to here {co_name} {state}"), - } - self.ready.push_back(co); - } - } - Ok(()) - } - - /// Schedule the coroutines. - /// - /// Allow multiple threads to concurrently submit coroutine to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// # Errors - /// see `try_timeout_schedule`. - pub fn try_schedule(&self) -> std::io::Result<()> { - self.try_timeout_schedule(std::time::Duration::MAX.as_secs()) - .map(|_| ()) - } - - /// Try scheduling the coroutines for up to `dur`. - /// - /// Allow multiple threads to concurrently submit coroutine to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// # Errors - /// see `try_timeout_schedule`. - pub fn try_timed_schedule(&self, dur: std::time::Duration) -> std::io::Result { - self.try_timeout_schedule(open_coroutine_timer::get_timeout_time(dur)) - } - - /// Attempt to schedule the coroutines before the `timeout_time` timestamp. - /// - /// Allow multiple threads to concurrently submit coroutine to the scheduler, - /// but only allow one thread to execute scheduling. - /// - /// Returns the left time in ns. - /// - /// # Errors - /// if change to ready fails. - pub fn try_timeout_schedule(&self, timeout_time: u64) -> std::io::Result { - if self - .scheduling - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_err() - { - return Ok(timeout_time.saturating_sub(open_coroutine_timer::now())); - } - Self::init_current(self); - let left_time = self.do_schedule(timeout_time); - Self::clean_current(); - self.scheduling.store(false, Ordering::Release); - left_time - } - - fn do_schedule(&self, timeout_time: u64) -> std::io::Result { - loop { - let left_time = timeout_time.saturating_sub(open_coroutine_timer::now()); - if left_time == 0 { - return Ok(0); - } - self.check_ready()?; - // schedule coroutines - if let Some(mut coroutine) = self.ready.pop_front() { - match coroutine.resume() { - Ok(state) => match state { - CoroutineState::SystemCall((), _, state) => { - //挂起协程到系统调用表 - let co_name = Box::leak(Box::from(coroutine.get_name())); - //如果已包含,说明当前系统调用还有上层父系统调用,因此直接忽略插入结果 - unsafe { - _ = SYSTEM_CALL_TABLE.insert(co_name, coroutine); - if let SyscallState::Suspend(timestamp) = state { - SYSTEM_CALL_SUSPEND_TABLE.insert(timestamp, co_name); - } - } - } - CoroutineState::Suspend((), timestamp) => { - if timestamp > open_coroutine_timer::now() { - //挂起协程到时间轮 - unsafe { SUSPEND_TABLE.insert(timestamp, coroutine) }; - } else { - //放入就绪队列尾部 - self.ready.push_back(coroutine); - } - } - CoroutineState::Complete(result) => { - let co_name = Box::leak(Box::from(coroutine.get_name())); - assert!( - self.results.insert(co_name, Ok(result)).is_none(), - "not consume result" - ); - } - CoroutineState::Error(message) => { - let co_name = Box::leak(Box::from(coroutine.get_name())); - assert!( - self.results.insert(co_name, Err(message)).is_none(), - "not consume result" - ); - } - _ => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "try_timeout_schedule should never execute to here", - )); - } - }, - Err(e) => { - error!("{} resume failed: {:?}", coroutine.get_name(), e); - return Err(e); - } - }; - } else { - return Ok(left_time); - } - } - } - - fn check_ready(&self) -> std::io::Result<()> { - unsafe { - for _ in 0..SUSPEND_TABLE.len() { - if let Some((exec_time, _)) = SUSPEND_TABLE.front() { - if open_coroutine_timer::now() < *exec_time { - break; - } - //移动至"就绪"队列 - if let Some((_, mut entry)) = SUSPEND_TABLE.pop_front() { - for _ in 0..entry.len() { - if let Some(coroutine) = entry.pop_front() { - coroutine.ready()?; - //把到时间的协程加入就绪队列 - self.ready.push_back(coroutine); - } - } - } - } - } - // Check if the elements in the syscall suspend queue are ready - for _ in 0..SYSTEM_CALL_SUSPEND_TABLE.entry_len() { - if let Some((exec_time, _)) = SYSTEM_CALL_SUSPEND_TABLE.front() { - if open_coroutine_timer::now() < *exec_time { - break; - } - if let Some((_, mut entry)) = SYSTEM_CALL_SUSPEND_TABLE.pop_front() { - while let Some(co_name) = entry.pop_front() { - if let Some(coroutine) = SYSTEM_CALL_TABLE.remove(&co_name) { - match coroutine.state() { - CoroutineState::SystemCall(val, syscall, state) => { - if let SyscallState::Suspend(_) = state { - coroutine.syscall( - val, - syscall, - SyscallState::Timeout, - )?; - } - self.ready.push_back(coroutine); - } - _ => { - unreachable!("check_ready should never execute to here") - } - } - } - } - } - } - } - } - Ok(()) - } - - /// Attempt to obtain coroutine result with the given `co_name`. - pub fn try_get_co_result(&self, co_name: &str) -> Option, &'s str>> { - self.results.remove(co_name).map(|r| r.1) - } - - /// Returns `true` if the ready queue, suspend queue, and syscall queue are all empty. - pub fn is_empty(&self) -> bool { - self.size() == 0 - } - - /// Returns the number of coroutines owned by this scheduler. - pub fn size(&self) -> usize { - self.ready.len() + unsafe { SUSPEND_TABLE.len() + SYSTEM_CALL_TABLE.len() } - } - - /// Add a listener to this scheduler. - pub fn add_listener(&mut self, listener: impl SchedulableListener + 's) { - self.listeners.push_back(Box::leak(Box::new(listener))); - } -} - -impl Default for Scheduler<'_> { - fn default() -> Self { - Self::new( - format!("open-coroutine-scheduler-{:?}", std::thread::current().id()), - crate::constants::DEFAULT_STACK_SIZE, - ) - } -} - -impl Drop for Scheduler<'_> { - fn drop(&mut self) { - if !std::thread::panicking() { - assert!( - self.ready.is_empty(), - "There are still coroutines to be carried out in the ready queue:{:#?} !", - self.ready - ); - } - } -} - -impl Named for Scheduler<'_> { - fn get_name(&self) -> &str { - &self.name - } -} - -impl_for_named!(Scheduler<'s>); - -impl_current_for!(SCHEDULER, Scheduler<'s>); - -impl<'s, DerefScheduler: Deref>> Named for DerefScheduler { - fn get_name(&self) -> &str { - Box::leak(Box::from(self.deref().get_name())) - } -} diff --git a/open-coroutine-core/src/scheduler/tests.rs b/open-coroutine-core/src/scheduler/tests.rs deleted file mode 100644 index d25a67d6..00000000 --- a/open-coroutine-core/src/scheduler/tests.rs +++ /dev/null @@ -1,167 +0,0 @@ -use super::*; -use crate::constants::Syscall; -use std::time::Duration; - -#[test] -fn test_simple() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - println!("1"); - None - }, - None, - )?; - _ = scheduler.submit_co( - |_, _| { - println!("2"); - None - }, - None, - )?; - scheduler.try_schedule() -} - -#[test] -fn test_backtrace() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co(|_, _| None, None)?; - _ = scheduler.submit_co( - |_, _| { - println!("{:?}", backtrace::Backtrace::new()); - None - }, - None, - )?; - scheduler.try_schedule() -} - -#[test] -fn with_suspend() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |suspender, _| { - println!("[coroutine1] suspend"); - suspender.suspend(); - println!("[coroutine1] back"); - None - }, - None, - )?; - _ = scheduler.submit_co( - |suspender, _| { - println!("[coroutine2] suspend"); - suspender.suspend(); - println!("[coroutine2] back"); - None - }, - None, - )?; - scheduler.try_schedule() -} - -#[test] -fn with_delay() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |suspender, _| { - println!("[coroutine] delay"); - suspender.delay(Duration::from_millis(100)); - println!("[coroutine] back"); - None - }, - None, - )?; - scheduler.try_schedule()?; - std::thread::sleep(Duration::from_millis(100)); - scheduler.try_schedule() -} - -#[test] -fn test_state() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - if let Some(co) = SchedulableCoroutine::current() { - match co.state() { - CoroutineState::Running => println!("syscall nanosleep started !"), - _ => unreachable!("test_state 1 should never execute to here"), - }; - let timeout_time = - open_coroutine_timer::get_timeout_time(Duration::from_millis(10)); - co.syscall((), Syscall::nanosleep, SyscallState::Suspend(timeout_time)) - .expect("change to syscall state failed !"); - if let Some(suspender) = SchedulableSuspender::current() { - suspender.suspend(); - } - } - if let Some(co) = SchedulableCoroutine::current() { - match co.state() { - CoroutineState::SystemCall((), Syscall::nanosleep, SyscallState::Timeout) => { - println!("syscall nanosleep finished !"); - co.syscall((), Syscall::nanosleep, SyscallState::Executing) - .expect("change to syscall state failed !"); - co.running().expect("change to running state failed !"); - } - _ => unreachable!("test_state 2 should never execute to here"), - }; - } - None - }, - None, - )?; - scheduler.try_schedule()?; - std::thread::sleep(Duration::from_millis(10)); - scheduler.try_schedule() -} - -#[cfg(not(all( - target_os = "linux", - target_arch = "aarch64", - feature = "preemptive-schedule" -)))] -#[test] -fn test_trap() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - println!("Before trap"); - unsafe { std::ptr::write_volatile(1 as *mut u8, 0) }; - println!("After trap"); - None - }, - None, - )?; - _ = scheduler.submit_co( - |_, _| { - println!("200"); - None - }, - None, - )?; - scheduler.try_schedule() -} - -#[cfg(not(debug_assertions))] -#[test] -fn test_invalid_memory_reference() -> std::io::Result<()> { - let scheduler = Scheduler::default(); - _ = scheduler.submit_co( - |_, _| { - println!("Before invalid memory reference"); - // 没有加--release运行,会收到SIGABRT信号,不好处理,直接禁用测试 - unsafe { _ = &*((1usize as *mut std::ffi::c_void).cast::()) }; - println!("After invalid memory reference"); - None - }, - None, - )?; - _ = scheduler.submit_co( - |_, _| { - println!("200"); - None - }, - None, - )?; - scheduler.try_schedule() -} diff --git a/open-coroutine-core/src/syscall/common.rs b/open-coroutine-core/src/syscall/common.rs deleted file mode 100644 index fc5308d3..00000000 --- a/open-coroutine-core/src/syscall/common.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::ffi::c_int; - -extern "C" { - #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks")))] - #[cfg_attr( - any( - target_os = "linux", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "l4re" - ), - link_name = "__errno_location" - )] - #[cfg_attr( - any( - target_os = "netbsd", - target_os = "openbsd", - target_os = "android", - target_os = "redox", - target_env = "newlib" - ), - link_name = "__errno" - )] - #[cfg_attr( - any(target_os = "solaris", target_os = "illumos"), - link_name = "___errno" - )] - #[cfg_attr( - any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "watchos" - ), - link_name = "__error" - )] - #[cfg_attr(target_os = "haiku", link_name = "_errnop")] - fn errno_location() -> *mut c_int; -} - -pub extern "C" fn reset_errno() { - set_errno(0); -} - -pub extern "C" fn set_errno(errno: c_int) { - unsafe { errno_location().write(errno) } -} - -/// # Panics -/// if set fails. -pub extern "C" fn set_non_blocking(socket: c_int) { - assert!( - set_non_blocking_flag(socket, true), - "set_non_blocking failed !" - ); -} - -/// # Panics -/// if set fails. -pub extern "C" fn set_blocking(socket: c_int) { - assert!( - set_non_blocking_flag(socket, false), - "set_blocking failed !" - ); -} - -extern "C" fn set_non_blocking_flag(socket: c_int, on: bool) -> bool { - let flags = unsafe { libc::fcntl(socket, libc::F_GETFL) }; - if flags < 0 { - return false; - } - unsafe { - libc::fcntl( - socket, - libc::F_SETFL, - if on { - flags | libc::O_NONBLOCK - } else { - flags & !libc::O_NONBLOCK - }, - ) == 0 - } -} - -#[must_use] -pub extern "C" fn is_blocking(socket: c_int) -> bool { - !is_non_blocking(socket) -} - -#[must_use] -pub extern "C" fn is_non_blocking(socket: c_int) -> bool { - let flags = unsafe { libc::fcntl(socket, libc::F_GETFL) }; - if flags < 0 { - return false; - } - (flags & libc::O_NONBLOCK) != 0 -} - -#[macro_export] -macro_rules! log_syscall { - ( $socket:expr, $done:expr, $once_result:expr ) => { - #[cfg(feature = "logs")] - if let Some(coroutine) = $crate::scheduler::SchedulableCoroutine::current() { - $crate::info!( - "{} {} {} {} {} {}", - coroutine.get_name(), - coroutine.state(), - $socket, - $done, - $once_result, - std::io::Error::last_os_error(), - ); - } - }; -} - -#[macro_export] -macro_rules! impl_non_blocking { - ( $socket:expr, $impls:expr ) => {{ - let socket = $socket; - let blocking = $crate::syscall::common::is_blocking(socket); - if blocking { - $crate::syscall::common::set_non_blocking(socket); - } - let r = $impls; - if blocking { - $crate::syscall::common::set_blocking(socket); - } - return r; - }}; -} diff --git a/open-coroutine-core/src/syscall/windows/Sleep.rs b/open-coroutine-core/src/syscall/windows/Sleep.rs deleted file mode 100644 index a7f50163..00000000 --- a/open-coroutine-core/src/syscall/windows/Sleep.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::common::{Current, Named}; -use crate::constants::{Syscall, SyscallState}; -use crate::net::event_loop::EventLoops; -use crate::scheduler::SchedulableCoroutine; -use once_cell::sync::Lazy; -use open_coroutine_timer::get_timeout_time; -use retour::StaticDetour; -use std::time::Duration; - -pub extern "C" fn Sleep( - fn_ptr: Option<&StaticDetour>, - dw_milliseconds: u32, -) { - static CHAIN: Lazy> = Lazy::new(Default::default); - CHAIN.Sleep(fn_ptr, dw_milliseconds); -} - -trait SleepSyscall { - extern "system" fn Sleep( - &self, - fn_ptr: Option<&StaticDetour>, - dw_milliseconds: u32, - ); -} - -impl_facade!(SleepSyscallFacade, SleepSyscall, - Sleep(dw_milliseconds: u32) -> () -); - -#[derive(Debug, Copy, Clone, Default)] -struct NioSleepSyscall {} - -impl SleepSyscall for NioSleepSyscall { - extern "system" fn Sleep( - &self, - _: Option<&StaticDetour>, - dw_milliseconds: u32, - ) { - let time = Duration::from_millis(u64::from(dw_milliseconds)); - if let Some(co) = SchedulableCoroutine::current() { - let syscall = Syscall::Sleep; - let new_state = SyscallState::Suspend(get_timeout_time(time)); - if co.syscall((), syscall, new_state).is_err() { - crate::error!( - "{} change to syscall {} {} failed !", - co.get_name(), - syscall, - new_state - ); - } - } - _ = EventLoops::wait_just(Some(time)); - } -} diff --git a/open-coroutine-core/src/syscall/windows/mod.rs b/open-coroutine-core/src/syscall/windows/mod.rs deleted file mode 100644 index bf009ac8..00000000 --- a/open-coroutine-core/src/syscall/windows/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub use Sleep::Sleep; - -macro_rules! impl_facade { - ( $struct_name:ident, $trait_name: ident, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { - #[derive(Debug, Default)] - struct $struct_name { - inner: I, - } - - impl $trait_name for $struct_name { - extern "system" fn $syscall( - &self, - fn_ptr: Option<&retour::StaticDetour $result>>, - $($arg: $arg_type),* - ) -> $result { - use $crate::constants::{Syscall, SyscallState}; - use $crate::common::{Current, Named}; - use $crate::scheduler::SchedulableCoroutine; - - let syscall = Syscall::$syscall; - $crate::info!("hook syscall {}", syscall); - if let Some(co) = SchedulableCoroutine::current() { - let new_state = SyscallState::Executing; - if co.syscall((), syscall, new_state).is_err() { - $crate::error!("{} change to syscall {} {} failed !", - co.get_name(), syscall, new_state); - } - } - let r = self.inner.$syscall(fn_ptr, $($arg, )*); - if let Some(co) = SchedulableCoroutine::current() { - if co.running().is_err() { - $crate::error!("{} change to running state failed !", co.get_name()); - } - } - r - } - } - } -} - -#[allow(unused_macros)] -macro_rules! impl_raw { - ( $struct_name: ident, $trait_name: ident, $($mod_name: ident)::*, $syscall: ident($($arg: ident : $arg_type: ty),*) -> $result: ty ) => { - #[derive(Debug, Copy, Clone, Default)] - struct $struct_name {} - - impl $trait_name for $struct_name { - extern "system" fn $syscall( - &self, - fn_ptr: Option<&retour::StaticDetour -> $result>, - $($arg: $arg_type),* - ) -> $result { - unsafe { - if let Some(f) = fn_ptr { - f.call($($arg),*) - } else { - $($mod_name)::*::$syscall($($arg),*) - } - } - } - } - } -} - -mod Sleep; diff --git a/open-coroutine-hooks/Cargo.toml b/open-coroutine-hooks/Cargo.toml deleted file mode 100644 index 95872e67..00000000 --- a/open-coroutine-hooks/Cargo.toml +++ /dev/null @@ -1,61 +0,0 @@ -[package] -name = "open-coroutine-hooks" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The syscall hook for open-coroutine" -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/open-coroutine-hooks" -keywords = ["open-coroutine", "hook", "syscall"] -categories = ["os", "concurrency", "asynchronous"] -license = "Apache-2.0" -readme = "../README.md" - -[dependencies] -libc = "0.2.150" -once_cell = "1.18.0" -num_cpus = "1.16.0" -cfg-if = "1.0.0" -open-coroutine-core = { version = "0.5.0", path = "../open-coroutine-core", features = [ - "syscall"], default-features = false } - -[target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59.0", features = [ - "Win32_Foundation", - "Win32_System_Diagnostics_Debug", - "Win32_System_Threading", - "Win32_Security", - "Win32_System_LibraryLoader", - "Win32_System_SystemServices" -] } -retour = { version = "0.3.1", features = ["static-detour"] } - -[lib] -crate-type = ["cdylib"] - -[features] -default = ["open-coroutine-core/default"] - -# Print some help log. -# Enable for default. -logs = ["open-coroutine-core/logs"] - -korosensei = ["open-coroutine-core/korosensei"] - -boost = ["open-coroutine-core/boost"] - -# Provide preemptive scheduling implementation. -# Enable for default. -preemptive-schedule = ["open-coroutine-core/preemptive-schedule"] - -# Provide net API abstraction and implementation. -net = ["open-coroutine-core/net"] - -# Provide io_uring abstraction and implementation. -# This feature only works in linux. -io_uring = ["open-coroutine-core/io_uring"] - -# Provide syscall implementation. -syscall = ["open-coroutine-core/syscall"] - -# Enable all features -full = ["open-coroutine-core/full"] diff --git a/open-coroutine-hooks/src/coroutine.rs b/open-coroutine-hooks/src/coroutine.rs deleted file mode 100644 index 9c170373..00000000 --- a/open-coroutine-hooks/src/coroutine.rs +++ /dev/null @@ -1,53 +0,0 @@ -use open_coroutine_core::common::JoinHandler; -use open_coroutine_core::net::event_loop::join::CoJoinHandle; -use open_coroutine_core::net::event_loop::{EventLoops, UserFunc}; -use std::ffi::{c_long, c_void}; -use std::time::Duration; - -///创建协程 -#[no_mangle] -pub extern "C" fn coroutine_crate(f: UserFunc, param: usize, stack_size: usize) -> CoJoinHandle { - let stack_size = if stack_size > 0 { - Some(stack_size) - } else { - None - }; - EventLoops::submit_co( - move |suspender, ()| { - #[allow(clippy::cast_ptr_alignment, clippy::ptr_as_ptr)] - Some(f(std::ptr::from_ref(suspender), param)) - }, - stack_size, - ) - .unwrap_or_else(|_| CoJoinHandle::err()) -} - -///等待协程完成 -#[no_mangle] -pub extern "C" fn coroutine_join(handle: CoJoinHandle) -> c_long { - match handle.join() { - Ok(ptr) => match ptr { - Ok(ptr) => match ptr { - Some(ptr) => ptr as *mut c_void as c_long, - None => 0, - }, - Err(_) => -1, - }, - Err(_) => -1, - } -} - -///等待协程完成 -#[no_mangle] -pub extern "C" fn coroutine_timeout_join(handle: &CoJoinHandle, ns_time: u64) -> c_long { - match handle.timeout_join(Duration::from_nanos(ns_time)) { - Ok(ptr) => match ptr { - Ok(ptr) => match ptr { - Some(ptr) => ptr as *mut c_void as c_long, - None => 0, - }, - Err(_) => -1, - }, - Err(_) => -1, - } -} diff --git a/open-coroutine-hooks/src/lib.rs b/open-coroutine-hooks/src/lib.rs deleted file mode 100644 index 77de0bd3..00000000 --- a/open-coroutine-hooks/src/lib.rs +++ /dev/null @@ -1,78 +0,0 @@ -#![deny( - // The following are allowed by default lints according to - // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html - anonymous_parameters, - bare_trait_objects, - // elided_lifetimes_in_paths, // allow anonymous lifetime - missing_copy_implementations, - missing_debug_implementations, - // missing_docs, // TODO: add documents - // single_use_lifetimes, // TODO: fix lifetime names only used once - // trivial_casts, - trivial_numeric_casts, - // unreachable_pub, allow clippy::redundant_pub_crate lint instead - // unsafe_code, - unstable_features, - unused_extern_crates, - unused_import_braces, - unused_qualifications, - unused_results, - variant_size_differences, - - warnings, // treat all wanings as errors - - clippy::all, - // clippy::restriction, - clippy::pedantic, - // clippy::nursery, // It's still under development - clippy::cargo, -)] -#![allow( - // Some explicitly allowed Clippy lints, must have clear reason to allow - clippy::blanket_clippy_restriction_lints, // allow clippy::restriction - clippy::implicit_return, // actually omitting the return keyword is idiomatic Rust code - clippy::module_name_repetitions, // repeation of module name in a struct name is not big deal - clippy::multiple_crate_versions, // multi-version dependency crates is not able to fix - clippy::missing_errors_doc, // TODO: add error docs - clippy::missing_panics_doc, // TODO: add panic docs - clippy::panic_in_result_fn, - clippy::shadow_same, // Not too much bad - clippy::shadow_reuse, // Not too much bad - clippy::exhaustive_enums, - clippy::exhaustive_structs, - clippy::indexing_slicing, - clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix - clippy::single_char_lifetime_names, // TODO: change lifetime names -)] - -use open_coroutine_core::net::config::Config; -use open_coroutine_core::net::event_loop::EventLoops; - -#[no_mangle] -pub extern "C" fn init_config(config: Config) { - //一方面保证hook的函数能够被重定向到(防止压根不调用coroutine_crate的情况) - //另一方面初始化EventLoop配置 - _ = Config::get_instance() - .set_event_loop_size(config.get_event_loop_size()) - .set_stack_size(config.get_stack_size()) - .set_min_size(config.get_min_size()) - .set_max_size(config.get_max_size()) - .set_keep_alive_time(config.get_keep_alive_time()); -} - -#[no_mangle] -pub extern "C" fn shutdowns() { - EventLoops::stop(); -} - -pub mod coroutine; - -pub mod task; - -#[allow(dead_code, clippy::not_unsafe_ptr_arg_deref, clippy::similar_names)] -#[cfg(unix)] -pub mod unix; - -#[allow(dead_code, clippy::not_unsafe_ptr_arg_deref, clippy::similar_names)] -#[cfg(windows)] -mod windows; diff --git a/open-coroutine-hooks/src/task.rs b/open-coroutine-hooks/src/task.rs deleted file mode 100644 index 37b435c9..00000000 --- a/open-coroutine-hooks/src/task.rs +++ /dev/null @@ -1,47 +0,0 @@ -use open_coroutine_core::common::JoinHandler; -use open_coroutine_core::net::event_loop::join::TaskJoinHandle; -use open_coroutine_core::net::event_loop::{EventLoops, UserFunc}; -use std::ffi::{c_long, c_void}; -use std::time::Duration; - -///创建任务 -#[no_mangle] -pub extern "C" fn task_crate(f: UserFunc, param: usize) -> TaskJoinHandle { - EventLoops::submit( - move |suspender, p| { - #[allow(clippy::cast_ptr_alignment, clippy::ptr_as_ptr)] - Some(f(std::ptr::from_ref(suspender), p.unwrap_or(0))) - }, - Some(param), - ) -} - -///等待任务完成 -#[no_mangle] -pub extern "C" fn task_join(handle: TaskJoinHandle) -> c_long { - match handle.join() { - Ok(ptr) => match ptr { - Ok(ptr) => match ptr { - Some(ptr) => ptr as *mut c_void as c_long, - None => 0, - }, - Err(_) => -1, - }, - Err(_) => -1, - } -} - -///等待任务完成 -#[no_mangle] -pub extern "C" fn task_timeout_join(handle: &TaskJoinHandle, ns_time: u64) -> c_long { - match handle.timeout_join(Duration::from_nanos(ns_time)) { - Ok(ptr) => match ptr { - Ok(ptr) => match ptr { - Some(ptr) => ptr as *mut c_void as c_long, - None => 0, - }, - Err(_) => -1, - }, - Err(_) => -1, - } -} diff --git a/open-coroutine-hooks/src/unix/common.rs b/open-coroutine-hooks/src/unix/common.rs deleted file mode 100644 index b37638d8..00000000 --- a/open-coroutine-hooks/src/unix/common.rs +++ /dev/null @@ -1,31 +0,0 @@ -use libc::{c_int, fd_set, nfds_t, pollfd, timeval}; -use once_cell::sync::Lazy; - -static POLL: Lazy c_int> = init_hook!("poll"); - -#[no_mangle] -pub extern "C" fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int { - open_coroutine_core::syscall::poll(Some(Lazy::force(&POLL)), fds, nfds, timeout) -} - -static SELECT: Lazy< - extern "C" fn(c_int, *mut fd_set, *mut fd_set, *mut fd_set, *mut timeval) -> c_int, -> = init_hook!("select"); - -#[no_mangle] -pub extern "C" fn select( - nfds: c_int, - readfds: *mut fd_set, - writefds: *mut fd_set, - errorfds: *mut fd_set, - timeout: *mut timeval, -) -> c_int { - open_coroutine_core::syscall::select( - Some(Lazy::force(&SELECT)), - nfds, - readfds, - writefds, - errorfds, - timeout, - ) -} diff --git a/open-coroutine-hooks/src/unix/linux_like.rs b/open-coroutine-hooks/src/unix/linux_like.rs deleted file mode 100644 index fd92f8ea..00000000 --- a/open-coroutine-hooks/src/unix/linux_like.rs +++ /dev/null @@ -1,15 +0,0 @@ -use libc::{c_int, sockaddr, socklen_t}; -use once_cell::sync::Lazy; - -static ACCEPT4: Lazy c_int> = - init_hook!("accept4"); - -#[no_mangle] -pub extern "C" fn accept4( - fd: c_int, - addr: *mut sockaddr, - len: *mut socklen_t, - flg: c_int, -) -> c_int { - open_coroutine_core::syscall::accept4(Some(Lazy::force(&ACCEPT4)), fd, addr, len, flg) -} diff --git a/open-coroutine-hooks/src/unix/mod.rs b/open-coroutine-hooks/src/unix/mod.rs deleted file mode 100644 index 15395074..00000000 --- a/open-coroutine-hooks/src/unix/mod.rs +++ /dev/null @@ -1,123 +0,0 @@ -// check https://www.rustwiki.org.cn/en/reference/introduction.html for help information -macro_rules! init_hook { - ( $symbol:literal ) => { - once_cell::sync::Lazy::new(|| unsafe { - let syscall = $symbol; - let symbol = std::ffi::CString::new(String::from(syscall)) - .unwrap_or_else(|_| panic!("can not transfer \"{syscall}\" to CString")); - let ptr = libc::dlsym(libc::RTLD_NEXT, symbol.as_ptr()); - assert!(!ptr.is_null(), "system call \"{syscall}\" not found !"); - std::mem::transmute(ptr) - }) - }; -} - -pub mod common; - -pub mod sleep; - -pub mod socket; - -pub mod read; - -pub mod write; - -#[cfg(any( - target_os = "linux", - target_os = "l4re", - target_os = "android", - target_os = "emscripten" -))] -mod linux_like; - -extern "C" { - #[cfg(not(any(target_os = "dragonfly", target_os = "vxworks")))] - #[cfg_attr( - any( - target_os = "linux", - target_os = "emscripten", - target_os = "fuchsia", - target_os = "l4re" - ), - link_name = "__errno_location" - )] - #[cfg_attr( - any( - target_os = "netbsd", - target_os = "openbsd", - target_os = "android", - target_os = "redox", - target_env = "newlib" - ), - link_name = "__errno" - )] - #[cfg_attr( - any(target_os = "solaris", target_os = "illumos"), - link_name = "___errno" - )] - #[cfg_attr( - any( - target_os = "macos", - target_os = "ios", - target_os = "freebsd", - target_os = "watchos" - ), - link_name = "__error" - )] - #[cfg_attr(target_os = "haiku", link_name = "_errnop")] - fn errno_location() -> *mut libc::c_int; -} - -pub extern "C" fn reset_errno() { - set_errno(0); -} - -pub extern "C" fn set_errno(errno: libc::c_int) { - unsafe { errno_location().write(errno) } -} - -extern "C" fn set_non_blocking(socket: libc::c_int) { - assert!( - set_non_blocking_flag(socket, true), - "set_non_blocking failed !" - ); -} - -extern "C" fn set_blocking(socket: libc::c_int) { - assert!( - set_non_blocking_flag(socket, false), - "set_blocking failed !" - ); -} - -extern "C" fn set_non_blocking_flag(socket: libc::c_int, on: bool) -> bool { - let flags = unsafe { libc::fcntl(socket, libc::F_GETFL) }; - if flags < 0 { - return false; - } - unsafe { - libc::fcntl( - socket, - libc::F_SETFL, - if on { - flags | libc::O_NONBLOCK - } else { - flags & !libc::O_NONBLOCK - }, - ) == 0 - } -} - -#[must_use] -pub extern "C" fn is_blocking(socket: libc::c_int) -> bool { - !is_non_blocking(socket) -} - -#[must_use] -pub extern "C" fn is_non_blocking(socket: libc::c_int) -> bool { - let flags = unsafe { libc::fcntl(socket, libc::F_GETFL) }; - if flags < 0 { - return false; - } - (flags & libc::O_NONBLOCK) != 0 -} diff --git a/open-coroutine-hooks/src/unix/read.rs b/open-coroutine-hooks/src/unix/read.rs deleted file mode 100644 index fda8b270..00000000 --- a/open-coroutine-hooks/src/unix/read.rs +++ /dev/null @@ -1,64 +0,0 @@ -use libc::{iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; -use once_cell::sync::Lazy; -use std::ffi::{c_int, c_void}; - -static RECV: Lazy ssize_t> = init_hook!("recv"); - -#[no_mangle] -pub extern "C" fn recv(socket: c_int, buf: *mut c_void, len: size_t, flags: c_int) -> ssize_t { - open_coroutine_core::syscall::recv(Some(Lazy::force(&RECV)), socket, buf, len, flags) -} - -static RECVFROM: Lazy< - extern "C" fn(c_int, *mut c_void, size_t, c_int, *mut sockaddr, *mut socklen_t) -> ssize_t, -> = init_hook!("recvfrom"); - -#[no_mangle] -pub extern "C" fn recvfrom( - socket: c_int, - buf: *mut c_void, - len: size_t, - flags: c_int, - addr: *mut sockaddr, - addrlen: *mut socklen_t, -) -> ssize_t { - open_coroutine_core::syscall::recvfrom( - Some(Lazy::force(&RECVFROM)), - socket, - buf, - len, - flags, - addr, - addrlen, - ) -} - -static PREAD: Lazy ssize_t> = - init_hook!("pread"); - -#[no_mangle] -pub extern "C" fn pread(fd: c_int, buf: *mut c_void, count: size_t, offset: off_t) -> ssize_t { - open_coroutine_core::syscall::pread(Some(Lazy::force(&PREAD)), fd, buf, count, offset) -} - -static READV: Lazy ssize_t> = init_hook!("readv"); - -#[no_mangle] -pub extern "C" fn readv(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { - open_coroutine_core::syscall::readv(Some(Lazy::force(&READV)), fd, iov, iovcnt) -} - -static PREADV: Lazy ssize_t> = - init_hook!("preadv"); - -#[no_mangle] -pub extern "C" fn preadv(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t { - open_coroutine_core::syscall::preadv(Some(Lazy::force(&PREADV)), fd, iov, iovcnt, offset) -} - -static RECVMSG: Lazy ssize_t> = init_hook!("recvmsg"); - -#[no_mangle] -pub extern "C" fn recvmsg(fd: c_int, msg: *mut msghdr, flags: c_int) -> ssize_t { - open_coroutine_core::syscall::recvmsg(Some(Lazy::force(&RECVMSG)), fd, msg, flags) -} diff --git a/open-coroutine-hooks/src/unix/sleep.rs b/open-coroutine-hooks/src/unix/sleep.rs deleted file mode 100644 index e1ef90f6..00000000 --- a/open-coroutine-hooks/src/unix/sleep.rs +++ /dev/null @@ -1,26 +0,0 @@ -use libc::timespec; -use once_cell::sync::Lazy; -use std::ffi::{c_int, c_uint}; - -static SLEEP: Lazy c_uint> = init_hook!("sleep"); - -#[no_mangle] -pub extern "C" fn sleep(secs: c_uint) -> c_uint { - open_coroutine_core::syscall::sleep(Some(Lazy::force(&SLEEP)), secs) -} - -static USLEEP: Lazy c_int> = init_hook!("usleep"); - -#[no_mangle] -pub extern "C" fn usleep(secs: c_uint) -> c_int { - open_coroutine_core::syscall::usleep(Some(Lazy::force(&USLEEP)), secs) -} - -static NANOSLEEP: Lazy c_int> = - init_hook!("nanosleep"); - -#[no_mangle] -#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] -pub extern "C" fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int { - open_coroutine_core::syscall::nanosleep(Some(Lazy::force(&NANOSLEEP)), rqtp, rmtp) -} diff --git a/open-coroutine-hooks/src/unix/socket.rs b/open-coroutine-hooks/src/unix/socket.rs deleted file mode 100644 index 4089895c..00000000 --- a/open-coroutine-hooks/src/unix/socket.rs +++ /dev/null @@ -1,44 +0,0 @@ -use libc::{sockaddr, socklen_t}; -use once_cell::sync::Lazy; -use std::ffi::c_int; - -static SOCKET: Lazy c_int> = init_hook!("socket"); - -#[no_mangle] -pub extern "C" fn socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int { - open_coroutine_core::syscall::socket(Some(Lazy::force(&SOCKET)), domain, ty, protocol) -} - -static CONNECT: Lazy c_int> = - init_hook!("connect"); - -#[no_mangle] -pub extern "C" fn connect(socket: c_int, address: *const sockaddr, len: socklen_t) -> c_int { - open_coroutine_core::syscall::connect(Some(Lazy::force(&CONNECT)), socket, address, len) -} - -static LISTEN: Lazy c_int> = init_hook!("listen"); - -#[no_mangle] -pub extern "C" fn listen(socket: c_int, backlog: c_int) -> c_int { - open_coroutine_core::syscall::listen(Some(Lazy::force(&LISTEN)), socket, backlog) -} - -static ACCEPT: Lazy c_int> = - init_hook!("accept"); - -#[no_mangle] -pub extern "C" fn accept( - socket: c_int, - address: *mut sockaddr, - address_len: *mut socklen_t, -) -> c_int { - open_coroutine_core::syscall::accept(Some(Lazy::force(&ACCEPT)), socket, address, address_len) -} - -static SHUTDOWN: Lazy c_int> = init_hook!("shutdown"); - -#[no_mangle] -pub extern "C" fn shutdown(socket: c_int, how: c_int) -> c_int { - open_coroutine_core::syscall::shutdown(Some(Lazy::force(&SHUTDOWN)), socket, how) -} diff --git a/open-coroutine-hooks/src/unix/write.rs b/open-coroutine-hooks/src/unix/write.rs deleted file mode 100644 index 19fc9b9b..00000000 --- a/open-coroutine-hooks/src/unix/write.rs +++ /dev/null @@ -1,65 +0,0 @@ -use libc::{iovec, msghdr, off_t, size_t, sockaddr, socklen_t, ssize_t}; -use once_cell::sync::Lazy; -use std::ffi::{c_int, c_void}; - -static SEND: Lazy ssize_t> = - init_hook!("send"); - -#[no_mangle] -pub extern "C" fn send(socket: c_int, buf: *const c_void, len: size_t, flags: c_int) -> ssize_t { - open_coroutine_core::syscall::send(Some(Lazy::force(&SEND)), socket, buf, len, flags) -} - -static SENDTO: Lazy< - extern "C" fn(c_int, *const c_void, size_t, c_int, *const sockaddr, socklen_t) -> ssize_t, -> = init_hook!("sendto"); - -#[no_mangle] -pub extern "C" fn sendto( - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - addr: *const sockaddr, - addrlen: socklen_t, -) -> ssize_t { - open_coroutine_core::syscall::sendto( - Some(Lazy::force(&SENDTO)), - socket, - buf, - len, - flags, - addr, - addrlen, - ) -} - -static PWRITE: Lazy ssize_t> = - init_hook!("pwrite"); - -#[no_mangle] -pub extern "C" fn pwrite(fd: c_int, buf: *const c_void, count: size_t, offset: off_t) -> ssize_t { - open_coroutine_core::syscall::pwrite(Some(Lazy::force(&PWRITE)), fd, buf, count, offset) -} - -static WRITEV: Lazy ssize_t> = init_hook!("writev"); - -#[no_mangle] -pub extern "C" fn writev(fd: c_int, iov: *const iovec, iovcnt: c_int) -> ssize_t { - open_coroutine_core::syscall::writev(Some(Lazy::force(&WRITEV)), fd, iov, iovcnt) -} - -static PWRITEV: Lazy ssize_t> = - init_hook!("pwritev"); - -#[no_mangle] -pub extern "C" fn pwritev(fd: c_int, iov: *const iovec, iovcnt: c_int, offset: off_t) -> ssize_t { - open_coroutine_core::syscall::pwritev(Some(Lazy::force(&PWRITEV)), fd, iov, iovcnt, offset) -} - -static SENDMSG: Lazy ssize_t> = init_hook!("sendmsg"); - -#[no_mangle] -pub extern "C" fn sendmsg(fd: c_int, msg: *const msghdr, flags: c_int) -> ssize_t { - open_coroutine_core::syscall::sendmsg(Some(Lazy::force(&SENDMSG)), fd, msg, flags) -} diff --git a/open-coroutine-hooks/src/windows/mod.rs b/open-coroutine-hooks/src/windows/mod.rs deleted file mode 100644 index edc48a45..00000000 --- a/open-coroutine-hooks/src/windows/mod.rs +++ /dev/null @@ -1,66 +0,0 @@ -use retour::static_detour; -use std::error::Error; -use std::os::raw::c_void; -use std::{ffi::CString, iter, mem}; -use windows_sys::Win32::Foundation::BOOL; -use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress}; -use windows_sys::Win32::System::SystemServices::DLL_PROCESS_ATTACH; - -#[no_mangle] -#[allow(non_snake_case, warnings)] -pub unsafe extern "system" fn DllMain( - _module: *mut c_void, - call_reason: u32, - _reserved: *mut c_void, -) -> BOOL { - if call_reason == DLL_PROCESS_ATTACH { - // A console may be useful for printing to 'stdout' - // winapi::um::consoleapi::AllocConsole(); - - // Preferably a thread should be created here instead, since as few - // operations as possible should be performed within `DllMain`. - BOOL::from(main().is_ok()) - } else { - 1 - } -} - -static_detour! { - static SleepHook: unsafe extern "system" fn(u32); -} - -// A type alias for `FnSleep` (makes the transmute easy on the eyes) -type FnSleep = unsafe extern "system" fn(u32); - -/// Called when the DLL is attached to the process. -unsafe fn main() -> Result<(), Box> { - // Retrieve an absolute address of `MessageBoxW`. This is required for - // libraries due to the import address table. If `MessageBoxW` would be - // provided directly as the target, it would only hook this DLL's - // `MessageBoxW`. Using the method below an absolute address is retrieved - // instead, detouring all invocations of `MessageBoxW` in the active process. - let address = - get_module_symbol_address("kernel32.dll", "Sleep").expect("could not find 'Sleep' address"); - let target: FnSleep = mem::transmute(address); - - // Initialize AND enable the detour (the 2nd parameter can also be a closure) - SleepHook.initialize(target, sleep_detour)?.enable()?; - Ok(()) -} - -/// Returns a module symbol's absolute address. -fn get_module_symbol_address(module: &str, symbol: &str) -> Option { - let module = module - .encode_utf16() - .chain(iter::once(0)) - .collect::>(); - let symbol = CString::new(symbol).unwrap(); - unsafe { - let handle = GetModuleHandleW(module.as_ptr()); - GetProcAddress(handle, symbol.as_ptr().cast()).map(|n| n as usize) - } -} - -fn sleep_detour(dw_milliseconds: u32) { - open_coroutine_core::syscall::Sleep(Some(&SleepHook), dw_milliseconds); -} diff --git a/open-coroutine-iouring/Cargo.toml b/open-coroutine-iouring/Cargo.toml deleted file mode 100644 index 972b3086..00000000 --- a/open-coroutine-iouring/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "open-coroutine-iouring" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The io_uring support for open-coroutine" -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/open-coroutine-iouring" -keywords = ["open-coroutine", "iouring", "io_uring", "io-uring"] -categories = ["concurrency", "asynchronous"] -license = "Apache-2.0" -readme = "../README.md" - -[target.'cfg(target_os = "linux")'.dependencies] -io-uring = "0.6.1" -libc = "0.2.147" -cfg-if = "1.0.0" -once_cell = "1.18.0" - -[build-dependencies] -cfg-if = "1.0.0" - -[target.'cfg(target_os = "linux")'.build-dependencies] -cc = "1.0.82" - -[dev-dependencies] -anyhow = "1.0.75" -slab = "0.4.8" diff --git a/open-coroutine-iouring/cpp_src/version.h b/open-coroutine-iouring/cpp_src/version.h deleted file mode 100644 index 5df292fa..00000000 --- a/open-coroutine-iouring/cpp_src/version.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __SORTING_H__ -#define __SORTING_H__ "version.h" - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -int linux_version_code(); - -#ifdef __cplusplus -} -#endif -#endif \ No newline at end of file diff --git a/open-coroutine-iouring/src/io_uring.rs b/open-coroutine-iouring/src/io_uring.rs deleted file mode 100644 index 92d55de0..00000000 --- a/open-coroutine-iouring/src/io_uring.rs +++ /dev/null @@ -1,671 +0,0 @@ -use io_uring::opcode::{ - Accept, AsyncCancel, Close, Connect, EpollCtl, Fsync, MkDirAt, OpenAt, PollAdd, PollRemove, - Read, Readv, Recv, RecvMsg, RenameAt, Send, SendMsg, SendZc, Shutdown, Socket, Timeout, - TimeoutRemove, TimeoutUpdate, Write, Writev, -}; -use io_uring::squeue::Entry; -use io_uring::types::{epoll_event, Fd, Timespec}; -use io_uring::{CompletionQueue, IoUring, Probe}; -use libc::{ - c_char, c_int, c_uint, c_void, iovec, mode_t, msghdr, off_t, size_t, sockaddr, socklen_t, -}; -use once_cell::sync::Lazy; -use std::collections::VecDeque; -use std::fmt::{Debug, Formatter}; -use std::io::{Error, ErrorKind}; -use std::sync::Mutex; -use std::time::Duration; - -pub struct IoUringOperator { - io_uring: IoUring, - backlog: Mutex>, -} - -impl Debug for IoUringOperator { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IoUringSelector") - .field("backlog", &self.backlog) - .finish() - } -} - -static PROBE: Lazy = Lazy::new(|| { - let mut probe = Probe::new(); - if let Ok(io_uring) = IoUring::new(2) { - if let Ok(()) = io_uring.submitter().register_probe(&mut probe) { - return probe; - } - } - panic!("probe init failed !") -}); - -// check https://www.rustwiki.org.cn/en/reference/introduction.html for help information -macro_rules! support { - ( $opcode:ident ) => { - once_cell::sync::Lazy::new(|| { - if crate::version::support_io_uring() { - return PROBE.is_supported(io_uring::opcode::$opcode::CODE); - } - false - }) - }; -} - -static SUPPORT_ASYNC_CANCEL: Lazy = support!(AsyncCancel); - -static SUPPORT_OPENAT: Lazy = support!(OpenAt); - -static SUPPORT_MK_DIR_AT: Lazy = support!(MkDirAt); - -static SUPPORT_RENAME_AT: Lazy = support!(RenameAt); - -static SUPPORT_FSYNC: Lazy = support!(Fsync); - -static SUPPORT_TIMEOUT_ADD: Lazy = support!(Timeout); - -static SUPPORT_TIMEOUT_UPDATE: Lazy = support!(TimeoutUpdate); - -static SUPPORT_TIMEOUT_REMOVE: Lazy = support!(TimeoutRemove); - -static SUPPORT_EPOLL_CTL: Lazy = support!(EpollCtl); - -static SUPPORT_POLL_ADD: Lazy = support!(PollAdd); - -static SUPPORT_POLL_REMOVE: Lazy = support!(PollRemove); - -static SUPPORT_SOCKET: Lazy = support!(Socket); - -static SUPPORT_ACCEPT: Lazy = support!(Accept); - -static SUPPORT_CONNECT: Lazy = support!(Connect); - -static SUPPORT_SHUTDOWN: Lazy = support!(Shutdown); - -static SUPPORT_CLOSE: Lazy = support!(Close); - -static SUPPORT_RECV: Lazy = support!(Recv); - -static SUPPORT_READ: Lazy = support!(Read); - -static SUPPORT_READV: Lazy = support!(Readv); - -static SUPPORT_RECVMSG: Lazy = support!(RecvMsg); - -static SUPPORT_SEND: Lazy = support!(Send); - -static SUPPORT_SEND_ZC: Lazy = support!(SendZc); - -static SUPPORT_WRITE: Lazy = support!(Write); - -static SUPPORT_WRITEV: Lazy = support!(Writev); - -static SUPPORT_SENDMSG: Lazy = support!(SendMsg); - -impl IoUringOperator { - pub fn new(_cpu: u32) -> std::io::Result { - Ok(IoUringOperator { - io_uring: IoUring::builder().build(1024)?, - backlog: Mutex::new(VecDeque::new()), - }) - } - - fn push_sq(&self, entry: Entry) -> std::io::Result<()> { - let entry = Box::leak(Box::new(entry)); - if unsafe { self.io_uring.submission_shared().push(entry).is_err() } { - self.backlog.lock().unwrap().push_back(entry); - } - self.io_uring.submit().map(|_| ()) - } - - pub fn async_cancel(&self, user_data: usize) -> std::io::Result<()> { - if *SUPPORT_ASYNC_CANCEL { - let entry = AsyncCancel::new(user_data as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// select impl - - pub fn select(&self, timeout: Option) -> std::io::Result<(usize, CompletionQueue)> { - if crate::version::support_io_uring() { - let mut sq = unsafe { self.io_uring.submission_shared() }; - let mut cq = unsafe { self.io_uring.completion_shared() }; - if sq.is_empty() { - return Ok((0, cq)); - } - self.timeout_add(0, timeout)?; - let count = match self.io_uring.submit_and_wait(1) { - Ok(count) => count, - Err(err) => { - if err.raw_os_error() == Some(libc::EBUSY) { - 0 - } else { - return Err(err); - } - } - }; - cq.sync(); - - // clean backlog - loop { - if sq.is_full() { - match self.io_uring.submit() { - Ok(_) => (), - Err(err) => { - if err.raw_os_error() == Some(libc::EBUSY) { - break; - } - return Err(err); - } - } - } - sq.sync(); - - let mut backlog = self.backlog.lock().unwrap(); - match backlog.pop_front() { - Some(sqe) => { - if unsafe { sq.push(sqe).is_err() } { - backlog.push_front(sqe); - break; - } - } - None => break, - } - } - return Ok((count, cq)); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// poll - - pub fn epoll_ctl( - &self, - user_data: usize, - epfd: c_int, - op: c_int, - fd: c_int, - event: *mut libc::epoll_event, - ) -> std::io::Result<()> { - if *SUPPORT_EPOLL_CTL { - let entry = EpollCtl::new( - Fd(epfd), - Fd(fd), - op, - event as *const _ as u64 as *const epoll_event, - ) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn poll_add(&self, user_data: usize, fd: c_int, flags: c_int) -> std::io::Result<()> { - if *SUPPORT_POLL_ADD { - let entry = PollAdd::new(Fd(fd), flags as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn poll_remove(&self, user_data: usize) -> std::io::Result<()> { - if *SUPPORT_POLL_REMOVE { - let entry = PollRemove::new(user_data as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// timeout - - pub fn timeout_add(&self, user_data: usize, timeout: Option) -> std::io::Result<()> { - if let Some(duration) = timeout { - if *SUPPORT_TIMEOUT_ADD { - let timeout = Timespec::new() - .sec(duration.as_secs()) - .nsec(duration.subsec_nanos()); - let entry = Timeout::new(&timeout).build().user_data(user_data as u64); - return self.push_sq(entry); - } - return Err(Error::new(ErrorKind::Unsupported, "unsupported")); - } - Ok(()) - } - - pub fn timeout_update( - &self, - user_data: usize, - timeout: Option, - ) -> std::io::Result<()> { - if let Some(duration) = timeout { - if *SUPPORT_TIMEOUT_UPDATE { - let timeout = Timespec::new() - .sec(duration.as_secs()) - .nsec(duration.subsec_nanos()); - let entry = TimeoutUpdate::new(user_data as u64, &timeout) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - return Err(Error::new(ErrorKind::Unsupported, "unsupported")); - } - self.timeout_remove(user_data) - } - - pub fn timeout_remove(&self, user_data: usize) -> std::io::Result<()> { - if *SUPPORT_TIMEOUT_REMOVE { - let entry = TimeoutRemove::new(user_data as u64).build(); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// file IO - - pub fn openat( - &self, - user_data: usize, - dir_fd: c_int, - pathname: *const c_char, - flags: c_int, - mode: mode_t, - ) -> std::io::Result<()> { - if *SUPPORT_OPENAT { - let entry = OpenAt::new(Fd(dir_fd), pathname) - .flags(flags) - .mode(mode) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn mkdirat( - &self, - user_data: usize, - dir_fd: c_int, - pathname: *const c_char, - mode: mode_t, - ) -> std::io::Result<()> { - if *SUPPORT_MK_DIR_AT { - let entry = MkDirAt::new(Fd(dir_fd), pathname) - .mode(mode) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn renameat( - &self, - user_data: usize, - old_dir_fd: c_int, - old_path: *const c_char, - new_dir_fd: c_int, - new_path: *const c_char, - ) -> std::io::Result<()> { - if *SUPPORT_RENAME_AT { - let entry = RenameAt::new(Fd(old_dir_fd), old_path, Fd(new_dir_fd), new_path) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn renameat2( - &self, - user_data: usize, - old_dir_fd: c_int, - old_path: *const c_char, - new_dir_fd: c_int, - new_path: *const c_char, - flags: c_uint, - ) -> std::io::Result<()> { - if *SUPPORT_RENAME_AT { - let entry = RenameAt::new(Fd(old_dir_fd), old_path, Fd(new_dir_fd), new_path) - .flags(flags) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn fsync(&self, user_data: usize, fd: c_int) -> std::io::Result<()> { - if *SUPPORT_FSYNC { - let entry = Fsync::new(Fd(fd)).build().user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// socket - - pub fn socket( - &self, - user_data: usize, - domain: c_int, - ty: c_int, - protocol: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_SOCKET { - let entry = Socket::new(domain, ty, protocol) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn accept( - &self, - user_data: usize, - socket: c_int, - address: *mut sockaddr, - address_len: *mut socklen_t, - ) -> std::io::Result<()> { - if *SUPPORT_ACCEPT { - let entry = Accept::new(Fd(socket), address, address_len) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn accept4( - &self, - user_data: usize, - fd: c_int, - addr: *mut sockaddr, - len: *mut socklen_t, - flg: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_ACCEPT { - let entry = Accept::new(Fd(fd), addr, len) - .flags(flg) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn connect( - &self, - user_data: usize, - socket: c_int, - address: *const sockaddr, - len: socklen_t, - ) -> std::io::Result<()> { - if *SUPPORT_CONNECT { - let entry = Connect::new(Fd(socket), address, len) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn shutdown(&self, user_data: usize, socket: c_int, how: c_int) -> std::io::Result<()> { - if *SUPPORT_SHUTDOWN { - let entry = Shutdown::new(Fd(socket), how) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn close(&self, user_data: usize, fd: c_int) -> std::io::Result<()> { - if *SUPPORT_CLOSE { - let entry = Close::new(Fd(fd)).build().user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// read - - pub fn recv( - &self, - user_data: usize, - socket: c_int, - buf: *mut c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_RECV { - let entry = Recv::new(Fd(socket), buf.cast::(), len as u32) - .flags(flags) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn read( - &self, - user_data: usize, - fd: c_int, - buf: *mut c_void, - count: size_t, - ) -> std::io::Result<()> { - if *SUPPORT_READ { - let entry = Read::new(Fd(fd), buf.cast::(), count as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn pread( - &self, - user_data: usize, - fd: c_int, - buf: *mut c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result<()> { - if *SUPPORT_READ { - let entry = Read::new(Fd(fd), buf.cast::(), count as u32) - .offset(offset as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn readv( - &self, - user_data: usize, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_READV { - let entry = Readv::new(Fd(fd), iov, iovcnt as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn preadv( - &self, - user_data: usize, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result<()> { - if *SUPPORT_READV { - let entry = Readv::new(Fd(fd), iov, iovcnt as u32) - .offset(offset as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn recvmsg( - &self, - user_data: usize, - fd: c_int, - msg: *mut msghdr, - flags: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_RECVMSG { - let entry = RecvMsg::new(Fd(fd), msg) - .flags(flags as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - /// write - - pub fn send( - &self, - user_data: usize, - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_SEND { - let entry = Send::new(Fd(socket), buf.cast::(), len as u32) - .flags(flags) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - #[allow(clippy::too_many_arguments)] - pub fn sendto( - &self, - user_data: usize, - socket: c_int, - buf: *const c_void, - len: size_t, - flags: c_int, - addr: *const sockaddr, - addrlen: socklen_t, - ) -> std::io::Result<()> { - if *SUPPORT_SEND_ZC { - let entry = SendZc::new(Fd(socket), buf.cast::(), len as u32) - .flags(flags) - .dest_addr(addr) - .dest_addr_len(addrlen) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn write( - &self, - user_data: usize, - fd: c_int, - buf: *const c_void, - count: size_t, - ) -> std::io::Result<()> { - if *SUPPORT_WRITE { - let entry = Write::new(Fd(fd), buf.cast::(), count as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn pwrite( - &self, - user_data: usize, - fd: c_int, - buf: *const c_void, - count: size_t, - offset: off_t, - ) -> std::io::Result<()> { - if *SUPPORT_WRITE { - let entry = Write::new(Fd(fd), buf.cast::(), count as u32) - .offset(offset as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn writev( - &self, - user_data: usize, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_WRITEV { - let entry = Writev::new(Fd(fd), iov, iovcnt as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn pwritev( - &self, - user_data: usize, - fd: c_int, - iov: *const iovec, - iovcnt: c_int, - offset: off_t, - ) -> std::io::Result<()> { - if *SUPPORT_WRITEV { - let entry = Writev::new(Fd(fd), iov, iovcnt as u32) - .offset(offset as u64) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } - - pub fn sendmsg( - &self, - user_data: usize, - fd: c_int, - msg: *const msghdr, - flags: c_int, - ) -> std::io::Result<()> { - if *SUPPORT_SENDMSG { - let entry = SendMsg::new(Fd(fd), msg) - .flags(flags as u32) - .build() - .user_data(user_data as u64); - return self.push_sq(entry); - } - Err(Error::new(ErrorKind::Unsupported, "unsupported")) - } -} diff --git a/open-coroutine-iouring/src/lib.rs b/open-coroutine-iouring/src/lib.rs deleted file mode 100644 index f18fbced..00000000 --- a/open-coroutine-iouring/src/lib.rs +++ /dev/null @@ -1,424 +0,0 @@ -#[cfg(target_os = "linux")] -pub mod version; - -#[cfg(target_os = "linux")] -pub mod io_uring; - -#[cfg(all(target_os = "linux", test))] -mod tests { - use std::collections::VecDeque; - use std::io::{BufRead, BufReader, Write}; - use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, TcpStream}; - use std::os::unix::io::{AsRawFd, RawFd}; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - use std::time::Duration; - use std::{io, ptr}; - - use crate::io_uring::IoUringOperator; - use io_uring::{opcode, squeue, types, IoUring, SubmissionQueue}; - use slab::Slab; - - #[derive(Clone, Debug)] - enum Token { - Accept, - Poll { - fd: RawFd, - }, - Read { - fd: RawFd, - buf_index: usize, - }, - Write { - fd: RawFd, - buf_index: usize, - offset: usize, - len: usize, - }, - } - - pub struct AcceptCount { - entry: squeue::Entry, - count: usize, - } - - impl AcceptCount { - fn new(fd: RawFd, token: usize, count: usize) -> AcceptCount { - AcceptCount { - entry: opcode::Accept::new(types::Fd(fd), ptr::null_mut(), ptr::null_mut()) - .build() - .user_data(token as _), - count, - } - } - - pub fn push_to(&mut self, sq: &mut SubmissionQueue<'_>) { - while self.count > 0 { - unsafe { - match sq.push(&self.entry) { - Ok(_) => self.count -= 1, - Err(_) => break, - } - } - } - - sq.sync(); - } - } - - pub fn crate_server(port: u16, server_started: Arc) -> anyhow::Result<()> { - let mut ring: IoUring = IoUring::builder() - .setup_sqpoll(1000) - .setup_sqpoll_cpu(0) - .build(1024)?; - let listener = TcpListener::bind(("127.0.0.1", port))?; - - let mut backlog = VecDeque::new(); - let mut bufpool = Vec::with_capacity(64); - let mut buf_alloc = Slab::with_capacity(64); - let mut token_alloc = Slab::with_capacity(64); - - println!("listen {}", listener.local_addr()?); - server_started.store(true, Ordering::Release); - - let (submitter, mut sq, mut cq) = ring.split(); - - let mut accept = - AcceptCount::new(listener.as_raw_fd(), token_alloc.insert(Token::Accept), 1); - - accept.push_to(&mut sq); - - loop { - match submitter.submit_and_wait(1) { - Ok(_) => (), - Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => (), - Err(err) => return Err(err.into()), - } - cq.sync(); - - // clean backlog - loop { - if sq.is_full() { - match submitter.submit() { - Ok(_) => (), - Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => break, - Err(err) => return Err(err.into()), - } - } - sq.sync(); - - match backlog.pop_front() { - Some(sqe) => unsafe { - let _ = sq.push(&sqe); - }, - None => break, - } - } - - accept.push_to(&mut sq); - - for cqe in &mut cq { - let ret = cqe.result(); - let token_index = cqe.user_data() as usize; - - if ret < 0 { - eprintln!( - "token {:?} error: {:?}", - token_alloc.get(token_index), - io::Error::from_raw_os_error(-ret) - ); - continue; - } - - let token = &mut token_alloc[token_index]; - match token.clone() { - Token::Accept => { - println!("accept"); - - accept.count += 1; - - let fd = ret; - let poll_token = token_alloc.insert(Token::Poll { fd }); - - let poll_e = opcode::PollAdd::new(types::Fd(fd), libc::POLLIN as _) - .build() - .user_data(poll_token as _); - - unsafe { - if sq.push(&poll_e).is_err() { - backlog.push_back(poll_e); - } - } - } - Token::Poll { fd } => { - let (buf_index, buf) = match bufpool.pop() { - Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), - None => { - let buf = vec![0u8; 2048].into_boxed_slice(); - let buf_entry = buf_alloc.vacant_entry(); - let buf_index = buf_entry.key(); - (buf_index, buf_entry.insert(buf)) - } - }; - - *token = Token::Read { fd, buf_index }; - - let read_e = - opcode::Recv::new(types::Fd(fd), buf.as_mut_ptr(), buf.len() as _) - .build() - .user_data(token_index as _); - - unsafe { - if sq.push(&read_e).is_err() { - backlog.push_back(read_e); - } - } - } - Token::Read { fd, buf_index } => { - if ret == 0 { - bufpool.push(buf_index); - token_alloc.remove(token_index); - println!("shutdown connection"); - unsafe { libc::close(fd) }; - - println!("Server closed"); - return Ok(()); - } else { - let len = ret as usize; - let buf = &buf_alloc[buf_index]; - - *token = Token::Write { - fd, - buf_index, - len, - offset: 0, - }; - - let write_e = opcode::Send::new(types::Fd(fd), buf.as_ptr(), len as _) - .build() - .user_data(token_index as _); - - unsafe { - if sq.push(&write_e).is_err() { - backlog.push_back(write_e); - } - } - } - } - Token::Write { - fd, - buf_index, - offset, - len, - } => { - let write_len = ret as usize; - - let entry = if offset + write_len >= len { - bufpool.push(buf_index); - - *token = Token::Poll { fd }; - - opcode::PollAdd::new(types::Fd(fd), libc::POLLIN as _) - .build() - .user_data(token_index as _) - } else { - let offset = offset + write_len; - let len = len - offset; - - let buf = &buf_alloc[buf_index][offset..]; - - *token = Token::Write { - fd, - buf_index, - offset, - len, - }; - - opcode::Write::new(types::Fd(fd), buf.as_ptr(), len as _) - .build() - .user_data(token_index as _) - }; - - unsafe { - if sq.push(&entry).is_err() { - backlog.push_back(entry); - } - } - } - } - } - } - } - - pub fn crate_client(port: u16, server_started: Arc) { - //等服务端起来 - while !server_started.load(Ordering::Acquire) {} - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); - let mut stream = TcpStream::connect_timeout(&socket, Duration::from_secs(3)) - .unwrap_or_else(|_| panic!("connect to 127.0.0.1:3456 failed !")); - let mut data: [u8; 512] = [b'1'; 512]; - data[511] = b'\n'; - let mut buffer: Vec = Vec::with_capacity(512); - for _ in 0..3 { - //写入stream流,如果写入失败,提示"写入失败" - assert_eq!(512, stream.write(&data).expect("Failed to write!")); - print!("Client Send: {}", String::from_utf8_lossy(&data[..])); - - let mut reader = BufReader::new(&stream); - //一直读到换行为止(b'\n'中的b表示字节),读到buffer里面 - assert_eq!( - 512, - reader - .read_until(b'\n', &mut buffer) - .expect("Failed to read into buffer") - ); - print!("Client Received: {}", String::from_utf8_lossy(&buffer[..])); - assert_eq!(&data, &buffer as &[u8]); - buffer.clear(); - } - //发送终止符 - assert_eq!(1, stream.write(&[b'e']).expect("Failed to write!")); - println!("client closed"); - } - - #[test] - fn original() -> anyhow::Result<()> { - let port = 8488; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let handle = std::thread::spawn(move || crate_server(port, clone)); - std::thread::spawn(move || crate_client(port, server_started)) - .join() - .expect("client has error"); - handle.join().expect("server has error") - } - - pub fn crate_server2(port: u16, server_started: Arc) -> anyhow::Result<()> { - let operator = IoUringOperator::new(0)?; - let listener = TcpListener::bind(("127.0.0.1", port))?; - - let mut bufpool = Vec::with_capacity(64); - let mut buf_alloc = Slab::with_capacity(64); - let mut token_alloc = Slab::with_capacity(64); - - println!("listen {}", listener.local_addr()?); - server_started.store(true, Ordering::Release); - - operator.accept( - token_alloc.insert(Token::Accept), - listener.as_raw_fd(), - std::ptr::null_mut(), - std::ptr::null_mut(), - )?; - - loop { - let mut r = operator.select(None)?; - - for cqe in &mut r.1 { - let ret = cqe.result(); - let token_index = cqe.user_data() as usize; - - if ret < 0 { - eprintln!( - "token {:?} error: {:?}", - token_alloc.get(token_index), - io::Error::from_raw_os_error(-ret) - ); - continue; - } - - let token = &mut token_alloc[token_index]; - match token.clone() { - Token::Accept => { - println!("accept"); - - let fd = ret; - let poll_token = token_alloc.insert(Token::Poll { fd }); - - operator.poll_add(poll_token, fd, libc::POLLIN as _)?; - } - Token::Poll { fd } => { - let (buf_index, buf) = match bufpool.pop() { - Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), - None => { - let buf = vec![0u8; 2048].into_boxed_slice(); - let buf_entry = buf_alloc.vacant_entry(); - let buf_index = buf_entry.key(); - (buf_index, buf_entry.insert(buf)) - } - }; - - *token = Token::Read { fd, buf_index }; - - operator.recv(token_index, fd, buf.as_mut_ptr() as _, buf.len(), 0)?; - } - Token::Read { fd, buf_index } => { - if ret == 0 { - bufpool.push(buf_index); - token_alloc.remove(token_index); - println!("shutdown connection"); - unsafe { libc::close(fd) }; - - println!("Server closed"); - return Ok(()); - } else { - let len = ret as usize; - let buf = &buf_alloc[buf_index]; - - *token = Token::Write { - fd, - buf_index, - len, - offset: 0, - }; - - operator.send(token_index, fd, buf.as_ptr() as _, len, 0)?; - } - } - Token::Write { - fd, - buf_index, - offset, - len, - } => { - let write_len = ret as usize; - - if offset + write_len >= len { - bufpool.push(buf_index); - - *token = Token::Poll { fd }; - - operator.poll_add(token_index, fd, libc::POLLIN as _)?; - } else { - let offset = offset + write_len; - let len = len - offset; - - let buf = &buf_alloc[buf_index][offset..]; - - *token = Token::Write { - fd, - buf_index, - offset, - len, - }; - - operator.write(token_index, fd, buf.as_ptr() as _, len)?; - }; - } - } - } - } - } - - #[test] - fn framework() -> anyhow::Result<()> { - let port = 9898; - let server_started = Arc::new(AtomicBool::new(false)); - let clone = server_started.clone(); - let handle = std::thread::spawn(move || crate_server2(port, clone)); - std::thread::spawn(move || crate_client(port, server_started)) - .join() - .expect("client has error"); - handle.join().expect("server has error") - } -} diff --git a/open-coroutine-iouring/src/version.rs b/open-coroutine-iouring/src/version.rs deleted file mode 100644 index 9d3272a3..00000000 --- a/open-coroutine-iouring/src/version.rs +++ /dev/null @@ -1,34 +0,0 @@ -use once_cell::sync::Lazy; -use std::ffi::c_int; - -extern "C" { - fn linux_version_code() -> c_int; -} - -#[must_use] -pub fn kernel_version(major: c_int, patchlevel: c_int, sublevel: c_int) -> c_int { - ((major) << 16) + ((patchlevel) << 8) + if (sublevel) > 255 { 255 } else { sublevel } -} - -#[must_use] -pub fn current_kernel_version() -> c_int { - unsafe { linux_version_code() } -} - -static SUPPORT: Lazy = Lazy::new(|| current_kernel_version() >= kernel_version(5, 6, 0)); - -#[must_use] -pub fn support_io_uring() -> bool { - *SUPPORT -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test() { - println!("{}", current_kernel_version()); - println!("{}", support_io_uring()); - } -} diff --git a/open-coroutine-macros/Cargo.toml b/open-coroutine-macros/Cargo.toml deleted file mode 100644 index bd0623ad..00000000 --- a/open-coroutine-macros/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "open-coroutine-macros" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The proc macros for open-coroutine" -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/open-coroutine-macros" -keywords = ["open-coroutine", "macro"] -categories = ["concurrency", "asynchronous", "os", "network-programming", "wasm"] -license = "Apache-2.0" -readme = "../README.md" - -[dependencies] -num_cpus = "1.14.0" -syn = { version = "2.0.28", features = ["full"] } -quote = "1.0.32" - -[lib] -proc-macro = true \ No newline at end of file diff --git a/open-coroutine-queue/Cargo.toml b/open-coroutine-queue/Cargo.toml deleted file mode 100644 index 64ebf8b8..00000000 --- a/open-coroutine-queue/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "open-coroutine-queue" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "Concurrent work-stealing queue, implemented using st3 and crossbeam-deque." -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/open-coroutine-queue" -keywords = ["open-coroutine", "queue", "work-steal"] -categories = ["data-structures", "concurrency"] -license = "Apache-2.0" -readme = "../README.md" - -[dependencies] -num_cpus = "1.16.0" -st3 = "0.4.1" -crossbeam-deque = "0.8.3" -parking_lot = "0.12.1" \ No newline at end of file diff --git a/open-coroutine-queue/src/lib.rs b/open-coroutine-queue/src/lib.rs deleted file mode 100644 index c1b627ff..00000000 --- a/open-coroutine-queue/src/lib.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![deny( - // The following are allowed by default lints according to - // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html - anonymous_parameters, - bare_trait_objects, - elided_lifetimes_in_paths, - missing_copy_implementations, - missing_debug_implementations, - missing_docs, - single_use_lifetimes, - trivial_casts, - trivial_numeric_casts, - unreachable_pub, - unsafe_code, - unstable_features, - unused_extern_crates, - unused_import_braces, - unused_qualifications, - unused_results, - variant_size_differences, - warnings, // treat all wanings as errors - - clippy::all, - // clippy::restriction, - clippy::pedantic, - // clippy::nursery, // It's still under development - clippy::cargo, -)] -#![allow( - // Some explicitly allowed Clippy lints, must have clear reason to allow - clippy::blanket_clippy_restriction_lints, // allow clippy::restriction - clippy::implicit_return, // actually omitting the return keyword is idiomatic Rust code - clippy::module_name_repetitions, // repeation of module name in a struct name is not big deal - clippy::multiple_crate_versions, // multi-version dependency crates is not able to fix - clippy::panic_in_result_fn, - clippy::shadow_same, // Not too much bad - clippy::shadow_reuse, // Not too much bad - clippy::exhaustive_enums, - clippy::exhaustive_structs, - clippy::indexing_slicing, - clippy::wildcard_imports, - clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix -)] - -//! Suppose a thread in a work-stealing scheduler is idle and looking for the next task to run. To -//! find an available task, it might do the following: -//! -//! 1. Try popping one task from the local worker queue. -//! 2. Try popping and stealing tasks from another local worker queue. -//! 3. Try popping and stealing a batch of tasks from the global injector queue. -//! -//! An implementation of this work-stealing strategy: -//! -//! # Examples -//! -//! ``` -//! use open_coroutine_queue::WorkStealQueue; -//! -//! let queue = WorkStealQueue::new(2, 64); -//! queue.push(6); -//! queue.push(7); -//! -//! let local0 = queue.local_queue(); -//! local0.push_back(2); -//! local0.push_back(3); -//! local0.push_back(4); -//! local0.push_back(5); -//! -//! let local1 = queue.local_queue(); -//! local1.push_back(0); -//! local1.push_back(1); -//! for i in 0..8 { -//! assert_eq!(local1.pop_front(), Some(i)); -//! } -//! assert_eq!(local0.pop_front(), None); -//! assert_eq!(local1.pop_front(), None); -//! assert_eq!(queue.pop(), None); -//! ``` -//! - -pub use rand::*; -pub use work_steal::*; - -/// rand impl for work steal queue -#[allow(missing_docs)] -pub mod rand; - -/// work steal queue impl -pub mod work_steal; diff --git a/open-coroutine-queue/src/rand.rs b/open-coroutine-queue/src/rand.rs deleted file mode 100644 index 51743202..00000000 --- a/open-coroutine-queue/src/rand.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::cell::Cell; - -use parking_lot::Mutex; -use std::collections::hash_map::RandomState; -use std::hash::BuildHasher; -use std::sync::atomic::AtomicU32; -use std::sync::atomic::Ordering::Relaxed; - -static COUNTER: AtomicU32 = AtomicU32::new(1); - -pub(crate) fn seed() -> u64 { - let rand_state = RandomState::new(); - // Hash some unique-ish data to generate some new state - // Get the seed - rand_state.hash_one(COUNTER.fetch_add(1, Relaxed)) -} - -/// A deterministic generator for seeds (and other generators). -/// -/// Given the same initial seed, the generator will output the same sequence of seeds. -/// -/// Since the seed generator will be kept in a runtime handle, we need to wrap `FastRand` -/// in a Mutex to make it thread safe. Different to the `FastRand` that we keep in a -/// thread local store, the expectation is that seed generation will not need to happen -/// very frequently, so the cost of the mutex should be minimal. -#[repr(C)] -#[derive(Debug)] -pub struct RngSeedGenerator { - /// Internal state for the seed generator. We keep it in a Mutex so that we can safely - /// use it across multiple threads. - state: Mutex, -} - -impl RngSeedGenerator { - /// Returns a new generator from the provided seed. - #[must_use] - pub fn new(seed: RngSeed) -> Self { - Self { - state: Mutex::new(FastRand::new(seed)), - } - } - - /// Returns the next seed in the sequence. - pub fn next_seed(&self) -> RngSeed { - let rng = self.state.lock(); - - let s = rng.fastrand(); - let r = rng.fastrand(); - - RngSeed::from_pair(s, r) - } - - /// Directly creates a generator using the next seed. - #[must_use] - pub fn next_generator(&self) -> Self { - RngSeedGenerator::new(self.next_seed()) - } -} - -impl Default for RngSeedGenerator { - fn default() -> Self { - Self::new(RngSeed::new()) - } -} - -/// A seed for random number generation. -/// -/// In order to make certain functions within a runtime deterministic, a seed -/// can be specified at the time of creation. -#[allow(unreachable_pub)] -#[derive(Debug, Copy, Clone)] -pub struct RngSeed { - s: u32, - r: u32, -} - -impl RngSeed { - /// Creates a random seed using loom internally. - #[must_use] - pub fn new() -> Self { - Self::from_u64(seed()) - } - - #[allow(clippy::cast_possible_truncation)] - fn from_u64(seed: u64) -> Self { - let one = (seed >> 32) as u32; - let mut two = seed as u32; - - if two == 0 { - // This value cannot be zero - two = 1; - } - - Self::from_pair(one, two) - } - - fn from_pair(s: u32, r: u32) -> Self { - Self { s, r } - } -} - -impl Default for RngSeed { - fn default() -> Self { - Self::new() - } -} - -/// Fast random number generate. -/// -/// Implement xorshift64+: 2 32-bit xorshift sequences added together. -/// Shift triplet `[17,7,16]` was calculated as indicated in Marsaglia's -/// Xorshift paper: -/// This generator passes the `SmallCrush` suite, part of `TestU01` framework: -/// -#[repr(C)] -#[derive(Debug)] -pub struct FastRand { - one: Cell, - two: Cell, -} - -impl FastRand { - /// Initializes a new, thread-local, fast random number generator. - #[must_use] - pub fn new(seed: RngSeed) -> FastRand { - FastRand { - one: Cell::new(seed.s), - two: Cell::new(seed.r), - } - } - - /// Replaces the state of the random number generator with the provided seed, returning - /// the seed that represents the previous state of the random number generator. - /// - /// The random number generator will become equivalent to one created with - /// the same seed. - pub fn replace_seed(&self, seed: RngSeed) -> RngSeed { - let old_seed = RngSeed::from_pair(self.one.get(), self.two.get()); - - _ = self.one.replace(seed.s); - _ = self.two.replace(seed.r); - - old_seed - } - - pub fn fastrand_n(&self, n: u32) -> u32 { - // This is similar to fastrand() % n, but faster. - // See https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - let mul = (u64::from(self.fastrand())).wrapping_mul(u64::from(n)); - (mul >> 32) as u32 - } - - fn fastrand(&self) -> u32 { - let mut s1 = self.one.get(); - let s0 = self.two.get(); - - s1 ^= s1 << 17; - s1 = s1 ^ s0 ^ s1 >> 7 ^ s0 >> 16; - - self.one.set(s0); - self.two.set(s1); - - s0.wrapping_add(s1) - } -} diff --git a/open-coroutine-timer/Cargo.toml b/open-coroutine-timer/Cargo.toml deleted file mode 100644 index aa2c4f75..00000000 --- a/open-coroutine-timer/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "open-coroutine-timer" -version = "0.5.0" -edition = "2021" -authors = ["zhangzicheng@apache.org"] -description = "The time utils." -repository = "https://github.com/acl-dev/open-coroutine/tree/dev/open-coroutine-timer" -keywords = ["open-coroutine", "timer", "utils"] -categories = ["data-structures"] -license = "Apache-2.0" -readme = "../README.md" diff --git a/open-coroutine/Cargo.toml b/open-coroutine/Cargo.toml index d13c3714..8dacd906 100644 --- a/open-coroutine/Cargo.toml +++ b/open-coroutine/Cargo.toml @@ -1,56 +1,60 @@ [package] name = "open-coroutine" -version = "0.5.0" -edition = "2021" +version.workspace = true +edition.workspace = true authors = ["zhangzicheng@apache.org"] description = "The open-coroutine is a simple, efficient and generic stackful-coroutine library." repository = "https://github.com/acl-dev/open-coroutine" keywords = ["coroutine", "fiber", "stackful", "hook"] categories = ["data-structures", "concurrency", "asynchronous", "web-programming", "wasm"] -license = "Apache-2.0" -readme = "../README.md" +license.workspace = true +readme.workspace = true [dependencies] -libc = "0.2.119" -open-coroutine-core = { version = "0.5.0", path = "../open-coroutine-core" } -open-coroutine-hooks = { version = "0.5.0", path = "../open-coroutine-hooks" } -open-coroutine-macros = { version = "0.5.0", path = "../open-coroutine-macros" } - -[dev-dependencies] -# benchmark -criterion = "0.5.1" +libc.workspace = true +open-coroutine-core.workspace = true +open-coroutine-hook.workspace = true +open-coroutine-macros.workspace = true + +[target.'cfg(windows)'.dependencies] +windows-sys = { workspace = true, features = [ + "Win32_Foundation", + "Win32_System_Kernel", + "Win32_System_Threading", + "Win32_System_SystemInformation", + "Win32_System_Diagnostics_Debug", +] } [build-dependencies] -glob = "0.3.1" +tracing = { workspace = true, default-features = false } +tracing-subscriber = { workspace = true, features = [ + "fmt", + "local-time" +], default-features = false } +tracing-appender.workspace = true +time.workspace = true +cargo_metadata.workspace = true + +[dev-dependencies] +tempfile.workspace = true [features] -default = ["open-coroutine-hooks/default", "open-coroutine-core/default"] +default = ["open-coroutine-hook/default", "open-coroutine-core/default"] # Print some help log. # Enable for default. -logs = ["open-coroutine-hooks/logs", "open-coroutine-core/logs"] - -korosensei = ["open-coroutine-hooks/korosensei", "open-coroutine-core/korosensei"] - -boost = ["open-coroutine-hooks/boost", "open-coroutine-core/boost"] +log = ["open-coroutine-hook/log", "open-coroutine-core/log"] # Provide preemptive scheduling implementation. # Enable for default. -preemptive-schedule = ["open-coroutine-hooks/preemptive-schedule", "open-coroutine-core/preemptive-schedule"] +preemptive = ["open-coroutine-hook/preemptive", "open-coroutine-core/preemptive"] # Provide net API abstraction and implementation. -net = ["open-coroutine-hooks/net", "open-coroutine-core/net"] +net = ["open-coroutine-hook/net", "open-coroutine-core/net"] # Provide io_uring abstraction and implementation. # This feature only works in linux. -io_uring = ["open-coroutine-hooks/io_uring", "open-coroutine-core/io_uring"] +io_uring = ["open-coroutine-hook/io_uring", "open-coroutine-core/io_uring"] # Provide syscall implementation. -syscall = ["open-coroutine-hooks/syscall", "open-coroutine-core/syscall"] - -# Enable all features -full = ["open-coroutine-hooks/full", "open-coroutine-core/full"] - -[[bench]] -name = "benchmark" -harness = false +syscall = ["open-coroutine-hook/syscall", "open-coroutine-core/syscall"] diff --git a/open-coroutine/benches/benchmark.rs b/open-coroutine/benches/benchmark.rs deleted file mode 100644 index d3a8096a..00000000 --- a/open-coroutine/benches/benchmark.rs +++ /dev/null @@ -1,24 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; - -fn fibonacci(n: u64) -> u64 { - let mut a = 0; - let mut b = 1; - match n { - 0 => b, - _ => { - for _ in 0..n { - let c = a + b; - a = b; - b = c; - } - b - } - } -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/open-coroutine/build.rs b/open-coroutine/build.rs index 86d01452..a9bbd7e6 100644 --- a/open-coroutine/build.rs +++ b/open-coroutine/build.rs @@ -1,65 +1,186 @@ -use std::env; +use cargo_metadata::MetadataCommand; +use std::env::var; +use std::fs::{copy, read_dir}; use std::path::PathBuf; +use tracing::{info, Level}; +use tracing_appender::rolling::{RollingFileAppender, Rotation}; fn main() { - //fix dylib name - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + // init log + let out_dir = PathBuf::from(var("OUT_DIR").expect("OUT_DIR not found")); + let target_dir = out_dir + .parent() + .expect("can not find deps dir") + .parent() + .expect("can not find deps dir") + .parent() + .expect("can not find deps dir") + .parent() + .expect("can not find deps dir"); + _ = tracing_subscriber::fmt() + .with_writer(RollingFileAppender::new( + Rotation::NEVER, + target_dir, + "open-coroutine-build.log", + )) + .with_thread_names(true) + .with_line_number(true) + .with_max_level(Level::INFO) + .with_timer(tracing_subscriber::fmt::time::OffsetTime::new( + time::UtcOffset::from_hms(8, 0, 0).expect("create UtcOffset failed !"), + time::format_description::well_known::Rfc2822, + )) + .try_init(); + // build dylib + let target = var("TARGET").expect("env not found"); + let mut cargo = std::process::Command::new("cargo"); + let mut cmd = cargo.arg("build").arg("--target").arg(target.clone()); + if cfg!(not(debug_assertions)) { + cmd = cmd.arg("--release"); + } + let mut hook_toml = PathBuf::from(var("CARGO_MANIFEST_DIR").expect("env not found")) + .parent() + .expect("parent not found") + .join("hook") + .join("Cargo.toml"); + if !hook_toml.exists() { + info!( + "{:?} not exists, find open-coroutine-hook's Cargo.toml in $CARGO_HOME", + hook_toml + ); + // 使用cargo_metadata读到依赖版本,结合CARGO_HOME获取open-coroutine-hook的toml + let dep_src_dir = PathBuf::from(var("CARGO_HOME").expect("CARGO_HOME not found")) + .join("registry") + .join("src"); + let crates_parent_dirs = Vec::from_iter( + read_dir(dep_src_dir.clone()) + .expect("Failed to read deps") + .flatten(), + ); + let crates_parent = if crates_parent_dirs.len() == 1 { + crates_parent_dirs.first().expect("host dir not found") + } else { + let rustup_dist_server = + var("RUSTUP_DIST_SERVER").expect("RUSTUP_DIST_SERVER not found"); + let host = rustup_dist_server + .split("://") + .last() + .expect("host not found"); + crates_parent_dirs + .iter() + .find(|entry| { + entry + .file_name() + .to_string_lossy() + .to_string() + .contains(host) + }) + .unwrap_or_else(|| { + crates_parent_dirs + .iter() + .find(|entry| { + entry + .file_name() + .to_string_lossy() + .to_string() + .contains("crates.io") + }) + .expect("host dir not found") + }) + } + .file_name() + .to_string_lossy() + .to_string(); + info!("crates parent dirs:{:?}", crates_parent_dirs); + let metadata = MetadataCommand::default() + .no_deps() + .exec() + .expect("read cargo metadata failed"); + let package = metadata + .packages + .first() + .expect("read current package failed"); + info!("read package:{:#?}", package); + let dependency = package + .dependencies + .iter() + .find(|dep| dep.name.eq("open-coroutine-hook")) + .expect("open-coroutine-hook not found"); + let version = &dependency + .req + .comparators + .first() + .expect("version not found"); + hook_toml = dep_src_dir + .join(crates_parent) + .join(format!( + "open-coroutine-hook-{}.{}.{}", + version.major, + version.minor.unwrap_or(0), + version.patch.unwrap_or(0) + )) + .join("Cargo.toml"); + } + info!("open-coroutine-hook's Cargo.toml is here:{:?}", hook_toml); + assert!( + cmd.arg("--manifest-path") + .arg(hook_toml) + .arg("--target-dir") + .arg(out_dir.clone()) + .status() + .expect("failed to build dylib") + .success(), + "failed to build dylib" + ); + // correct dylib path + let hook_deps = out_dir + .join(target) + .join(if cfg!(debug_assertions) { + "debug" + } else { + "release" + }) + .join("deps"); let deps = out_dir .parent() - .unwrap() + .expect("can not find deps dir") .parent() - .unwrap() + .expect("can not find deps dir") .parent() - .unwrap() + .expect("can not find deps dir") .join("deps"); - let mut pattern = deps.to_str().unwrap().to_owned(); - if cfg!(target_os = "linux") { - pattern += "/libopen_coroutine_hooks*.so"; - for path in glob::glob(&pattern) - .expect("Failed to read glob pattern") - .flatten() - { - std::fs::rename(path, deps.join("libopen_coroutine_hooks.so")) - .expect("rename to libopen_coroutine_hooks.so failed!"); - } - } else if cfg!(target_os = "macos") { - pattern += "/libopen_coroutine_hooks*.dylib"; - for path in glob::glob(&pattern) - .expect("Failed to read glob pattern") - .flatten() - { - std::fs::rename(path, deps.join("libopen_coroutine_hooks.dylib")) - .expect("rename to libopen_coroutine_hooks.dylib failed!"); - } - } else if cfg!(target_os = "windows") { - let dll_pattern = pattern.clone() + "/open_coroutine_hooks*.dll"; - for path in glob::glob(&dll_pattern) - .expect("Failed to read glob pattern") - .flatten() - { - std::fs::rename(path, deps.join("open_coroutine_hooks.dll")) - .expect("rename to open_coroutine_hooks.dll failed!"); - } - - let lib_pattern = pattern.clone() + "/open_coroutine_hooks*.dll.lib"; - for path in glob::glob(&lib_pattern) - .expect("Failed to read glob pattern") - .flatten() - { - std::fs::rename(path, deps.join("open_coroutine_hooks.lib")) - .expect("rename to open_coroutine_hooks.lib failed!"); + for entry in read_dir(hook_deps.clone()) + .expect("can not find deps dir") + .flatten() + { + let file_name = entry.file_name().to_string_lossy().to_string(); + if !file_name.contains("open_coroutine_hook") { + continue; } - let lib_pattern = pattern + "/open_coroutine_hooks*.lib"; - for path in glob::glob(&lib_pattern) - .expect("Failed to read glob pattern") - .flatten() - { - std::fs::rename(path, deps.join("open_coroutine_hooks.lib")) - .expect("rename to open_coroutine_hooks.lib failed!"); + if cfg!(target_os = "linux") && file_name.ends_with(".so") { + let from = hook_deps.join(file_name); + let to = deps.join("libopen_coroutine_hook.so"); + copy(from.clone(), to.clone()).expect("copy to libopen_coroutine_hook.so failed!"); + info!("copy {:?} to {:?} success!", from, to); + } else if cfg!(target_os = "macos") && file_name.ends_with(".dylib") { + let from = hook_deps.join(file_name); + let to = deps.join("libopen_coroutine_hook.dylib"); + copy(from.clone(), to.clone()).expect("copy to libopen_coroutine_hook.dylib failed!"); + info!("copy {:?} to {:?} success!", from, to); + } else if cfg!(windows) { + if file_name.ends_with(".dll") { + let from = hook_deps.join(file_name); + let to = deps.join("open_coroutine_hook.dll"); + copy(from.clone(), to.clone()).expect("copy to open_coroutine_hook.dll failed!"); + info!("copy {:?} to {:?} success!", from, to); + } else if file_name.ends_with(".lib") { + let from = hook_deps.join(file_name); + let to = deps.join("open_coroutine_hook.lib"); + copy(from.clone(), to.clone()).expect("copy to open_coroutine_hook.lib failed!"); + info!("copy {:?} to {:?} success!", from, to); + } } - } else { - panic!("unsupported platform"); } - //link hook dylib - println!("cargo:rustc-link-lib=dylib=open_coroutine_hooks"); + // link dylib + println!("cargo:rustc-link-lib=dylib=open_coroutine_hook"); } diff --git a/open-coroutine/examples/file_co.rs b/open-coroutine/examples/file_co.rs new file mode 100644 index 00000000..97566867 --- /dev/null +++ b/open-coroutine/examples/file_co.rs @@ -0,0 +1,48 @@ +use open_coroutine::{task, JoinHandle}; +use std::fs::File; +use std::io::{Error, IoSlice, IoSliceMut, Read, Result, Seek, SeekFrom, Write}; + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() -> Result<()> { + let join_handle: JoinHandle> = task!( + |_| { + const HELLO: &str = "Hello World!"; + + // Write + let mut tmpfile: File = tempfile::tempfile()?; + assert_eq!(HELLO.len(), tmpfile.write(HELLO.as_ref())?); + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + // Read + let mut buf = String::new(); + assert_eq!(HELLO.len(), tmpfile.read_to_string(&mut buf)?); + assert_eq!(HELLO, buf); + + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + + // Write multiple + let ioslices = [IoSlice::new(HELLO.as_ref()), IoSlice::new(HELLO.as_ref())]; + assert_eq!(HELLO.len() * 2, tmpfile.write_vectored(&ioslices)?); + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + // Read multiple + let mut buf1 = [0; HELLO.len()]; + let mut buf2 = [0; HELLO.len()]; + let mut ioslicemuts = [IoSliceMut::new(&mut buf1), IoSliceMut::new(&mut buf2)]; + assert_eq!(HELLO.len() * 2, tmpfile.read_vectored(&mut ioslicemuts)?); + assert_eq!(HELLO, unsafe { std::str::from_utf8_unchecked(&mut buf1) }); + assert_eq!(HELLO, unsafe { std::str::from_utf8_unchecked(&mut buf2) }); + + Ok(()) + }, + () + ); + if let Some(r) = join_handle.join()? { + return r; + } + Err(Error::new( + std::io::ErrorKind::Other, + "Failed to join the task", + )) +} diff --git a/open-coroutine/examples/file_not_co.rs b/open-coroutine/examples/file_not_co.rs new file mode 100644 index 00000000..56e060fa --- /dev/null +++ b/open-coroutine/examples/file_not_co.rs @@ -0,0 +1,35 @@ +use std::fs::File; +use std::io::{IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() -> std::io::Result<()> { + const HELLO: &str = "Hello World!"; + + // Write + let mut tmpfile: File = tempfile::tempfile()?; + assert_eq!(HELLO.len(), tmpfile.write(HELLO.as_ref())?); + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + // Read + let mut buf = String::new(); + assert_eq!(HELLO.len(), tmpfile.read_to_string(&mut buf)?); + assert_eq!(HELLO, buf); + + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + + // Write multiple + let ioslices = [IoSlice::new(HELLO.as_ref()), IoSlice::new(HELLO.as_ref())]; + assert_eq!(HELLO.len() * 2, tmpfile.write_vectored(&ioslices)?); + // Seek to start + tmpfile.seek(SeekFrom::Start(0))?; + // Read multiple + let mut buf1 = [0; HELLO.len()]; + let mut buf2 = [0; HELLO.len()]; + let mut ioslicemuts = [IoSliceMut::new(&mut buf1), IoSliceMut::new(&mut buf2)]; + assert_eq!(HELLO.len() * 2, tmpfile.read_vectored(&mut ioslicemuts)?); + assert_eq!(HELLO, unsafe { std::str::from_utf8_unchecked(&mut buf1) }); + assert_eq!(HELLO, unsafe { std::str::from_utf8_unchecked(&mut buf2) }); + + Ok(()) +} diff --git a/open-coroutine/examples/scalable_stack.rs b/open-coroutine/examples/scalable_stack.rs new file mode 100644 index 00000000..d405fe9d --- /dev/null +++ b/open-coroutine/examples/scalable_stack.rs @@ -0,0 +1,27 @@ +use open_coroutine::{maybe_grow, task}; + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() { + let join = task!( + |_| { + fn recurse(i: u32, p: &mut [u8; 10240]) { + maybe_grow!(|| { + // Ensure the stack allocation isn't optimized away. + unsafe { _ = std::ptr::read_volatile(&p) }; + if i > 0 { + recurse(i - 1, &mut [0; 10240]); + } + }) + .expect("allocate stack failed") + } + println!("[coroutine] launched"); + // Use ~500KB of stack. + recurse(50, &mut [0; 10240]); + // Use ~500KB of stack. + recurse(50, &mut [0; 10240]); + println!("[coroutine] exited"); + }, + (), + ); + assert_eq!(Some(()), join.join().expect("join failed")); +} diff --git a/open-coroutine/examples/sleep_co.rs b/open-coroutine/examples/sleep_co.rs new file mode 100644 index 00000000..9242beac --- /dev/null +++ b/open-coroutine/examples/sleep_co.rs @@ -0,0 +1,44 @@ +use open_coroutine::task; +use open_coroutine_core::common::now; + +pub fn sleep_test_co(millis: u64) { + _ = task!( + move |_| { + let start = now(); + #[cfg(unix)] + std::thread::sleep(std::time::Duration::from_millis(millis)); + #[cfg(windows)] + unsafe { + windows_sys::Win32::System::Threading::Sleep(millis as u32); + } + let end = now(); + assert!(end - start >= millis, "Time consumption less than expected"); + println!("[coroutine1] {millis} launched"); + }, + (), + ); + _ = task!( + move |_| { + #[cfg(unix)] + std::thread::sleep(std::time::Duration::from_millis(500)); + #[cfg(windows)] + unsafe { + windows_sys::Win32::System::Threading::Sleep(500); + } + println!("[coroutine2] {millis} launched"); + }, + (), + ); + #[cfg(unix)] + std::thread::sleep(std::time::Duration::from_millis(millis + 500)); + #[cfg(windows)] + unsafe { + windows_sys::Win32::System::Threading::Sleep((millis + 500) as u32); + } +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 2)] +pub fn main() { + sleep_test_co(1); + sleep_test_co(1000); +} diff --git a/open-coroutine/examples/sleep_not_co.rs b/open-coroutine/examples/sleep_not_co.rs new file mode 100644 index 00000000..f1417c81 --- /dev/null +++ b/open-coroutine/examples/sleep_not_co.rs @@ -0,0 +1,32 @@ +use open_coroutine::task; +use open_coroutine_core::common::now; + +fn sleep_test(millis: u64) { + _ = task!( + move |_| { + println!("[coroutine1] {millis} launched"); + }, + (), + ); + _ = task!( + move |_| { + println!("[coroutine2] {millis} launched"); + }, + (), + ); + let start = now(); + #[cfg(unix)] + std::thread::sleep(std::time::Duration::from_millis(millis)); + #[cfg(windows)] + unsafe { + windows_sys::Win32::System::Threading::Sleep(millis as u32); + } + let end = now(); + assert!(end - start >= millis, "Time consumption less than expected"); +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 2)] +pub fn main() { + sleep_test(1); + sleep_test(1000); +} diff --git a/open-coroutine/examples/socket_co.rs b/open-coroutine/examples/socket_co.rs new file mode 100644 index 00000000..ca854fdb --- /dev/null +++ b/open-coroutine/examples/socket_co.rs @@ -0,0 +1,128 @@ +use open_coroutine::task; +use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Read, Write}; +use std::net::{Shutdown, TcpListener, ToSocketAddrs}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +pub fn start_co_server(addr: A, server_finished: Arc<(Mutex, Condvar)>) { + let listener = TcpListener::bind(addr).expect("start server failed"); + for stream in listener.incoming() { + _ = task!( + |mut socket| { + let mut buffer1 = [0; 256]; + for _ in 0..3 { + assert_eq!(12, socket.read(&mut buffer1).expect("recv failed")); + println!("Server Received: {}", String::from_utf8_lossy(&buffer1)); + assert_eq!(256, socket.write(&buffer1).expect("send failed")); + println!("Server Send"); + } + let mut buffer2 = [0; 256]; + for _ in 0..3 { + let mut buffers = + [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 26, + socket.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Server Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + let responses = [IoSlice::new(&buffer1), IoSlice::new(&buffer2)]; + assert_eq!( + 512, + socket.write_vectored(&responses).expect("writev failed") + ); + println!("Server Send Multiple"); + } + println!("Server Shutdown Write"); + if socket.shutdown(Shutdown::Write).is_ok() { + println!("Server Closed Connection"); + let (lock, cvar) = &*server_finished; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + println!("Server Closed"); + } + }, + stream.expect("accept new connection failed"), + ); + } +} + +pub fn start_co_client(addr: A) { + _ = task!( + |mut stream| { + let mut buffer1 = [0; 256]; + for i in 0..3 { + assert_eq!( + 12, + stream + .write(format!("RequestPart{i}").as_ref()) + .expect("send failed") + ); + println!("Client Send"); + assert_eq!(256, stream.read(&mut buffer1).expect("recv failed")); + println!("Client Received: {}", String::from_utf8_lossy(&buffer1)); + } + let mut buffer2 = [0; 256]; + for i in 0..3 { + let request1 = format!("RequestPart{i}1"); + let request2 = format!("RequestPart{i}2"); + let requests = [ + IoSlice::new(request1.as_ref()), + IoSlice::new(request2.as_ref()), + ]; + assert_eq!(26, stream.write_vectored(&requests).expect("writev failed")); + println!("Client Send Multiple"); + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 512, + stream.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Client Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + } + println!("Client Shutdown Write"); + stream.shutdown(Shutdown::Write).expect("shutdown failed"); + println!("Client Closed"); + }, + open_coroutine::connect_timeout(addr, Duration::from_secs(3)).expect("connect failed"), + ); +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 2)] +pub fn main() -> std::io::Result<()> { + let addr = "127.0.0.1:8999"; + let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); + let server_finished = Arc::clone(&server_finished_pair); + _ = std::thread::Builder::new() + .name("crate_co_server".to_string()) + .spawn(move || start_co_server(addr, server_finished_pair)) + .expect("failed to spawn thread"); + _ = std::thread::Builder::new() + .name("crate_co_client".to_string()) + .spawn(move || start_co_client(addr)) + .expect("failed to spawn thread"); + + let (lock, cvar) = &*server_finished; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + Duration::from_secs(30), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Err(Error::new( + ErrorKind::Other, + "The coroutine server and coroutine client did not completed within the specified time", + )) + } else { + Ok(()) + } +} diff --git a/open-coroutine/examples/socket_co_client.rs b/open-coroutine/examples/socket_co_client.rs new file mode 100644 index 00000000..a7d497ee --- /dev/null +++ b/open-coroutine/examples/socket_co_client.rs @@ -0,0 +1,124 @@ +use open_coroutine::task; +use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Read, Write}; +use std::net::{Shutdown, TcpListener, ToSocketAddrs}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +pub fn start_server(addr: A, server_finished: Arc<(Mutex, Condvar)>) { + let listener = TcpListener::bind(addr).expect("start server failed"); + for stream in listener.incoming() { + let mut socket = stream.expect("accept new connection failed"); + let mut buffer1 = [0; 256]; + for _ in 0..3 { + assert_eq!(12, socket.read(&mut buffer1).expect("recv failed")); + println!("Server Received: {}", String::from_utf8_lossy(&buffer1)); + assert_eq!(256, socket.write(&buffer1).expect("send failed")); + println!("Server Send"); + } + let mut buffer2 = [0; 256]; + for _ in 0..3 { + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 26, + socket.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Server Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + let responses = [IoSlice::new(&buffer1), IoSlice::new(&buffer2)]; + assert_eq!( + 512, + socket.write_vectored(&responses).expect("writev failed") + ); + println!("Server Send Multiple"); + } + println!("Server Shutdown Write"); + if socket.shutdown(Shutdown::Write).is_ok() { + println!("Server Closed Connection"); + let (lock, cvar) = &*server_finished; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + println!("Server Closed"); + return; + } + } +} + +pub fn start_co_client(addr: A) { + _ = task!( + |mut stream| { + let mut buffer1 = [0; 256]; + for i in 0..3 { + assert_eq!( + 12, + stream + .write(format!("RequestPart{i}").as_ref()) + .expect("send failed") + ); + println!("Client Send"); + assert_eq!(256, stream.read(&mut buffer1).expect("recv failed")); + println!("Client Received: {}", String::from_utf8_lossy(&buffer1)); + } + let mut buffer2 = [0; 256]; + for i in 0..3 { + let request1 = format!("RequestPart{i}1"); + let request2 = format!("RequestPart{i}2"); + let requests = [ + IoSlice::new(request1.as_ref()), + IoSlice::new(request2.as_ref()), + ]; + assert_eq!(26, stream.write_vectored(&requests).expect("writev failed")); + println!("Client Send Multiple"); + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 512, + stream.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Client Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + } + println!("Client Shutdown Write"); + stream.shutdown(Shutdown::Write).expect("shutdown failed"); + println!("Client Closed"); + }, + open_coroutine::connect_timeout(addr, Duration::from_secs(3)).expect("connect failed"), + ); +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() -> std::io::Result<()> { + let addr = "127.0.0.1:8899"; + let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); + let server_finished = Arc::clone(&server_finished_pair); + _ = std::thread::Builder::new() + .name("crate_server".to_string()) + .spawn(move || start_server(addr, server_finished_pair)) + .expect("failed to spawn thread"); + _ = std::thread::Builder::new() + .name("crate_co_client".to_string()) + .spawn(move || start_co_client(addr)) + .expect("failed to spawn thread"); + + let (lock, cvar) = &*server_finished; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + Duration::from_secs(30), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Err(Error::new( + ErrorKind::Other, + "The coroutine client did not completed within the specified time", + )) + } else { + Ok(()) + } +} diff --git a/open-coroutine/examples/socket_co_server.rs b/open-coroutine/examples/socket_co_server.rs new file mode 100644 index 00000000..888af1d5 --- /dev/null +++ b/open-coroutine/examples/socket_co_server.rs @@ -0,0 +1,125 @@ +use open_coroutine::task; +use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Read, Write}; +use std::net::{Shutdown, TcpListener, ToSocketAddrs}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +pub fn start_co_server(addr: A, server_finished: Arc<(Mutex, Condvar)>) { + let listener = TcpListener::bind(addr).expect("start server failed"); + for stream in listener.incoming() { + _ = task!( + |mut socket| { + let mut buffer1 = [0; 256]; + for _ in 0..3 { + assert_eq!(12, socket.read(&mut buffer1).expect("recv failed")); + println!("Server Received: {}", String::from_utf8_lossy(&buffer1)); + assert_eq!(256, socket.write(&buffer1).expect("send failed")); + println!("Server Send"); + } + let mut buffer2 = [0; 256]; + for _ in 0..3 { + let mut buffers = + [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 26, + socket.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Server Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + let responses = [IoSlice::new(&buffer1), IoSlice::new(&buffer2)]; + assert_eq!( + 512, + socket.write_vectored(&responses).expect("writev failed") + ); + println!("Server Send Multiple"); + } + println!("Server Shutdown Write"); + if socket.shutdown(Shutdown::Write).is_ok() { + println!("Server Closed Connection"); + let (lock, cvar) = &*server_finished; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + println!("Server Closed"); + } + }, + stream.expect("accept new connection failed"), + ); + } +} + +pub fn start_client(addr: A) { + let mut stream = + open_coroutine::connect_timeout(addr, Duration::from_secs(3)).expect("connect failed"); + let mut buffer1 = [0; 256]; + for i in 0..3 { + assert_eq!( + 12, + stream + .write(format!("RequestPart{i}").as_ref()) + .expect("send failed") + ); + println!("Client Send"); + assert_eq!(256, stream.read(&mut buffer1).expect("recv failed")); + println!("Client Received: {}", String::from_utf8_lossy(&buffer1)); + } + let mut buffer2 = [0; 256]; + for i in 0..3 { + let request1 = format!("RequestPart{i}1"); + let request2 = format!("RequestPart{i}2"); + let requests = [ + IoSlice::new(request1.as_ref()), + IoSlice::new(request2.as_ref()), + ]; + assert_eq!(26, stream.write_vectored(&requests).expect("writev failed")); + println!("Client Send Multiple"); + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 512, + stream.read_vectored(&mut buffers).expect("readv failed") + ); + println!( + "Client Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + } + println!("Client Shutdown Write"); + stream.shutdown(Shutdown::Write).expect("shutdown failed"); + println!("Client Closed"); +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() -> std::io::Result<()> { + let addr = "127.0.0.1:8889"; + let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); + let server_finished = Arc::clone(&server_finished_pair); + _ = std::thread::Builder::new() + .name("crate_co_server".to_string()) + .spawn(move || start_co_server(addr, server_finished_pair)) + .expect("failed to spawn thread"); + _ = std::thread::Builder::new() + .name("crate_client".to_string()) + .spawn(move || start_client(addr)) + .expect("failed to spawn thread"); + + let (lock, cvar) = &*server_finished; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + Duration::from_secs(30), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Err(Error::new( + ErrorKind::Other, + "The coroutine service did not completed within the specified time", + )) + } else { + Ok(()) + } +} diff --git a/open-coroutine/examples/socket_not_co.rs b/open-coroutine/examples/socket_not_co.rs new file mode 100644 index 00000000..4b71eeff --- /dev/null +++ b/open-coroutine/examples/socket_not_co.rs @@ -0,0 +1,120 @@ +use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Read, Write}; +use std::net::{Shutdown, TcpListener, ToSocketAddrs}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +fn start_server(addr: A, server_finished: Arc<(Mutex, Condvar)>) { + let listener = TcpListener::bind(addr).expect("start server failed"); + for stream in listener.incoming() { + let mut socket = stream.expect("accept new connection failed"); + let mut buffer1 = [0; 256]; + for _ in 0..3 { + assert_eq!(12, socket.read(&mut buffer1).expect("recv failed")); + eprintln!("Server Received: {}", String::from_utf8_lossy(&buffer1)); + assert_eq!(256, socket.write(&buffer1).expect("send failed")); + eprintln!("Server Send"); + } + let mut buffer2 = [0; 256]; + for _ in 0..3 { + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 26, + socket.read_vectored(&mut buffers).expect("readv failed") + ); + eprintln!( + "Server Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + let responses = [IoSlice::new(&buffer1), IoSlice::new(&buffer2)]; + assert_eq!( + 512, + socket.write_vectored(&responses).expect("writev failed") + ); + eprintln!("Server Send Multiple"); + } + eprintln!("Server Shutdown Write"); + if socket.shutdown(Shutdown::Write).is_ok() { + eprintln!("Server Closed Connection"); + let (lock, cvar) = &*server_finished; + let mut pending = lock.lock().unwrap(); + *pending = false; + cvar.notify_one(); + eprintln!("Server Closed"); + return; + } + } +} + +fn start_client(addr: A) { + let mut stream = + open_coroutine::connect_timeout(addr, Duration::from_secs(1)).expect("connect failed"); + let mut buffer1 = [0; 256]; + for i in 0..3 { + assert_eq!( + 12, + stream + .write(format!("RequestPart{i}").as_ref()) + .expect("send failed") + ); + eprintln!("Client Send"); + assert_eq!(256, stream.read(&mut buffer1).expect("recv failed")); + eprintln!("Client Received: {}", String::from_utf8_lossy(&buffer1)); + } + let mut buffer2 = [0; 256]; + for i in 0..3 { + let request1 = format!("RequestPart{i}1"); + let request2 = format!("RequestPart{i}2"); + let requests = [ + IoSlice::new(request1.as_ref()), + IoSlice::new(request2.as_ref()), + ]; + assert_eq!(26, stream.write_vectored(&requests).expect("writev failed")); + eprintln!("Client Send Multiple"); + let mut buffers = [IoSliceMut::new(&mut buffer1), IoSliceMut::new(&mut buffer2)]; + assert_eq!( + 512, + stream.read_vectored(&mut buffers).expect("readv failed") + ); + eprintln!( + "Client Received Multiple: {}{}", + String::from_utf8_lossy(&buffer1), + String::from_utf8_lossy(&buffer2) + ); + } + eprintln!("Client Shutdown Write"); + stream.shutdown(Shutdown::Write).expect("shutdown failed"); + eprintln!("Client Closed"); +} + +#[open_coroutine::main(event_loop_size = 1, max_size = 1)] +pub fn main() -> std::io::Result<()> { + let addr = "127.0.0.1:8888"; + let server_finished_pair = Arc::new((Mutex::new(true), Condvar::new())); + let server_finished = Arc::clone(&server_finished_pair); + _ = std::thread::Builder::new() + .name("crate_server".to_string()) + .spawn(move || start_server(addr, server_finished_pair)) + .expect("failed to spawn thread"); + _ = std::thread::Builder::new() + .name("crate_client".to_string()) + .spawn(move || start_client(addr)) + .expect("failed to spawn thread"); + + let (lock, cvar) = &*server_finished; + let result = cvar + .wait_timeout_while( + lock.lock().unwrap(), + Duration::from_secs(10), + |&mut pending| pending, + ) + .unwrap(); + if result.1.timed_out() { + Err(Error::new( + ErrorKind::Other, + "The service did not completed within the specified time", + )) + } else { + Ok(()) + } +} diff --git a/open-coroutine/src/coroutine.rs b/open-coroutine/src/coroutine.rs deleted file mode 100644 index 277c274e..00000000 --- a/open-coroutine/src/coroutine.rs +++ /dev/null @@ -1,148 +0,0 @@ -use open_coroutine_core::coroutine::suspender::Suspender; -use open_coroutine_core::net::event_loop::core::EventLoop; -use open_coroutine_core::net::event_loop::UserFunc; -use std::cmp::Ordering; -use std::ffi::{c_char, c_void}; -use std::io::{Error, ErrorKind}; -use std::time::Duration; - -#[allow(improper_ctypes)] -extern "C" { - fn coroutine_crate(f: UserFunc, param: usize, stack_size: usize) -> JoinHandle; - - fn coroutine_join(handle: JoinHandle) -> libc::c_long; - - fn coroutine_timeout_join(handle: &JoinHandle, ns_time: u64) -> libc::c_long; -} - -pub fn co(f: F, param: P, stack_size: usize) -> JoinHandle -where - F: FnOnce(*const Suspender<(), ()>, P) -> R + Copy, -{ - extern "C" fn co_main( - suspender: *const Suspender<(), ()>, - input: usize, - ) -> usize - where - F: FnOnce(*const Suspender<(), ()>, P) -> R + Copy, - { - unsafe { - let ptr = &mut *((input as *mut c_void).cast::<(F, P)>()); - let data = std::ptr::read_unaligned(ptr); - let result: &'static mut R = Box::leak(Box::new((data.0)(suspender, data.1))); - std::ptr::from_mut::(result).cast::() as usize - } - } - let inner = Box::leak(Box::new((f, param))); - unsafe { - coroutine_crate( - co_main::, - std::ptr::from_mut::<(F, P)>(inner).cast::() as usize, - stack_size, - ) - } -} - -#[macro_export] -macro_rules! co { - ( $f: expr , $param:expr $(,)? ) => {{ - $crate::coroutine::co( - $f, - $param, - //min stack size for backtrace - 64 * 1024, - ) - }}; - ( $f: expr , $param:expr ,$stack_size: expr $(,)?) => {{ - $crate::coroutine::co($f, $param, $stack_size) - }}; -} - -#[repr(C)] -#[derive(Debug)] -pub struct JoinHandle(*const EventLoop, *const c_char); - -impl JoinHandle { - #[allow(clippy::cast_possible_truncation)] - pub fn timeout_join(&self, dur: Duration) -> std::io::Result> { - unsafe { - let ptr = coroutine_timeout_join(self, dur.as_nanos() as u64); - match ptr.cmp(&0) { - Ordering::Less => Err(Error::new(ErrorKind::Other, "timeout join failed")), - Ordering::Equal => Ok(None), - Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), - } - } - } - - pub fn join(self) -> std::io::Result> { - unsafe { - let ptr = coroutine_join(self); - match ptr.cmp(&0) { - Ordering::Less => Err(Error::new(ErrorKind::Other, "join failed")), - Ordering::Equal => Ok(None), - Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), - } - } - } -} - -#[cfg(test)] -mod tests { - use std::sync::{Arc, Condvar, Mutex}; - use std::time::Duration; - - #[ignore] - #[test] - fn co_simplest() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let handler1 = co!( - |_, input| { - println!("[coroutine1] launched with {}", input); - input - }, - true, - 1024 * 1024, - ); - let handler2 = co!( - |_, input| { - println!("[coroutine2] launched with {}", input); - input - }, - "hello", - ); - assert_eq!(true, handler1.join().unwrap().unwrap()); - unsafe { assert_eq!("hello", &*handler2.join::<*mut str>().unwrap().unwrap()) }; - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "join failed", - )) - } else { - handler.join().unwrap(); - Ok(()) - } - } -} diff --git a/open-coroutine/src/lib.rs b/open-coroutine/src/lib.rs index f16b44dc..205ae160 100644 --- a/open-coroutine/src/lib.rs +++ b/open-coroutine/src/lib.rs @@ -6,9 +6,9 @@ // elided_lifetimes_in_paths, // allow anonymous lifetime missing_copy_implementations, missing_debug_implementations, - // missing_docs, // TODO: add documents - // single_use_lifetimes, // TODO: fix lifetime names only used once - // trivial_casts, + missing_docs, // TODO: add documents + single_use_lifetimes, // TODO: fix lifetime names only used once + trivial_casts, // TODO: remove trivial casts in code trivial_numeric_casts, // unreachable_pub, allow clippy::redundant_pub_crate lint instead // unsafe_code, @@ -26,6 +26,7 @@ clippy::pedantic, // clippy::nursery, // It's still under development clippy::cargo, + unreachable_pub, )] #![allow( // Some explicitly allowed Clippy lints, must have clear reason to allow @@ -44,34 +45,269 @@ clippy::separated_literal_suffix, // conflicts with clippy::unseparated_literal_suffix clippy::single_char_lifetime_names, // TODO: change lifetime names )] +//! see `https://github.com/acl-dev/open-coroutine` +use open_coroutine_core::co_pool::task::UserTaskFunc; +use open_coroutine_core::common::constants::SLICE; pub use open_coroutine_core::net::config::Config; +use open_coroutine_core::net::UserFunc; pub use open_coroutine_macros::*; +use std::cmp::Ordering; +use std::ffi::{c_int, c_longlong, c_uint, c_void}; +use std::io::{Error, ErrorKind}; +use std::marker::PhantomData; +use std::net::{TcpStream, ToSocketAddrs}; +use std::ops::Deref; +use std::time::Duration; -pub mod coroutine; +extern "C" { + fn open_coroutine_init(config: Config) -> c_int; + + fn open_coroutine_stop(secs: c_uint) -> c_int; -pub mod task; + fn maybe_grow_stack( + red_zone: usize, + stack_size: usize, + f: UserFunc, + param: usize, + ) -> c_longlong; +} +#[allow(improper_ctypes)] extern "C" { - fn init_config(config: Config); + fn task_crate(f: UserTaskFunc, param: usize) -> open_coroutine_core::net::join::JoinHandle; - fn shutdowns(); + fn task_join(handle: &open_coroutine_core::net::join::JoinHandle) -> c_longlong; + + fn task_timeout_join( + handle: &open_coroutine_core::net::join::JoinHandle, + ns_time: u64, + ) -> c_longlong; } +/// Init the open-coroutine. pub fn init(config: Config) { - unsafe { init_config(config) }; + assert_eq!( + 0, + unsafe { open_coroutine_init(config) }, + "open-coroutine init failed !" + ); } +/// Shutdown the open-coroutine. pub fn shutdown() { - unsafe { shutdowns() }; + unsafe { _ = open_coroutine_stop(30) }; +} + +/// Create a task. +#[macro_export] +macro_rules! task { + ( $f: expr , $param:expr $(,)? ) => { + $crate::task($f, $param) + }; +} + +/// Create a task. +pub fn task R>(f: F, param: P) -> JoinHandle { + extern "C" fn task_main R>(input: usize) -> usize { + unsafe { + let ptr = &mut *((input as *mut c_void).cast::<(F, P)>()); + let data = std::ptr::read_unaligned(ptr); + let result: &'static mut R = Box::leak(Box::new((data.0)(data.1))); + std::ptr::from_mut::(result).cast::() as usize + } + } + let inner = Box::leak(Box::new((f, param))); + unsafe { + task_crate( + task_main::, + std::ptr::from_mut::<(F, P)>(inner).cast::() as usize, + ) + .into() + } +} + +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug)] +pub struct JoinHandle(open_coroutine_core::net::join::JoinHandle, PhantomData); + +#[allow(missing_docs)] +impl JoinHandle { + #[allow(clippy::cast_possible_truncation)] + pub fn timeout_join(&self, dur: Duration) -> std::io::Result> { + unsafe { + let ptr = task_timeout_join(self, dur.as_nanos() as u64); + match ptr.cmp(&0) { + Ordering::Less => Err(Error::new(ErrorKind::Other, "timeout join failed")), + Ordering::Equal => Ok(None), + Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), + } + } + } + + pub fn join(self) -> std::io::Result> { + unsafe { + let ptr = task_join(&self); + match ptr.cmp(&0) { + Ordering::Less => Err(Error::new(ErrorKind::Other, "join failed")), + Ordering::Equal => Ok(None), + Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), + } + } + } +} + +impl From for JoinHandle { + fn from(val: open_coroutine_core::net::join::JoinHandle) -> Self { + Self(val, PhantomData) + } +} + +impl From> for open_coroutine_core::net::join::JoinHandle { + fn from(val: JoinHandle) -> Self { + val.0 + } +} + +impl Deref for JoinHandle { + type Target = open_coroutine_core::net::join::JoinHandle; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Grows the call stack if necessary. +#[macro_export] +macro_rules! maybe_grow { + ($red_zone:expr, $stack_size:expr, $f:expr $(,)?) => { + $crate::maybe_grow($red_zone, $stack_size, $f) + }; + ($stack_size:literal, $f:expr $(,)?) => { + $crate::maybe_grow( + open_coroutine_core::common::default_red_zone(), + $stack_size, + $f, + ) + }; + ($f:expr $(,)?) => { + $crate::maybe_grow( + open_coroutine_core::common::default_red_zone(), + open_coroutine_core::common::constants::DEFAULT_STACK_SIZE, + $f, + ) + }; +} + +/// Create a coroutine. +pub fn maybe_grow R>( + red_zone: usize, + stack_size: usize, + f: F, +) -> std::io::Result { + extern "C" fn execute_on_stack R>(input: usize) -> usize { + unsafe { + let ptr = &mut *((input as *mut c_void).cast::()); + let data = std::ptr::read_unaligned(ptr); + let result: &'static mut R = Box::leak(Box::new(data())); + std::ptr::from_mut::(result).cast::() as usize + } + } + let inner = Box::leak(Box::new(f)); + unsafe { + let ptr = maybe_grow_stack( + red_zone, + stack_size, + execute_on_stack::, + std::ptr::from_mut::(inner).cast::() as usize, + ); + if ptr < 0 { + return Err(Error::new(ErrorKind::InvalidInput, "grow stack failed")); + } + Ok(*Box::from_raw( + usize::try_from(ptr).expect("overflow") as *mut R + )) + } +} + +/// Opens a TCP connection to a remote host. +/// +/// `addr` is an address of the remote host. Anything which implements +/// [`ToSocketAddrs`] trait can be supplied for the address; see this trait +/// documentation for concrete examples. +/// +/// If `addr` yields multiple addresses, `connect` will be attempted with +/// each of the addresses until a connection is successful. If none of +/// the addresses result in a successful connection, the error returned from +/// the last connection attempt (the last address) is returned. +/// +/// # Examples +/// +/// Open a TCP connection to `127.0.0.1:8080`: +/// +/// ```no_run +/// if let Ok(stream) = open_coroutine::connect_timeout("127.0.0.1:8080", std::time::Duration::from_secs(3)) { +/// println!("Connected to the server!"); +/// } else { +/// println!("Couldn't connect to server..."); +/// } +/// ``` +/// +/// Open a TCP connection to `127.0.0.1:8080`. If the connection fails, open +/// a TCP connection to `127.0.0.1:8081`: +/// +/// ```no_run +/// use std::net::SocketAddr; +/// +/// let addrs = [ +/// SocketAddr::from(([127, 0, 0, 1], 8080)), +/// SocketAddr::from(([127, 0, 0, 1], 8081)), +/// ]; +/// if let Ok(stream) = open_coroutine::connect_timeout(&addrs[..], std::time::Duration::from_secs(3)) { +/// println!("Connected to the server!"); +/// } else { +/// println!("Couldn't connect to server..."); +/// } +/// ``` +pub fn connect_timeout(addr: A, timeout: Duration) -> std::io::Result { + let timeout_time = open_coroutine_core::common::get_timeout_time(timeout); + let mut last_err = None; + for addr in addr.to_socket_addrs()? { + loop { + let left_time = timeout_time.saturating_sub(open_coroutine_core::common::now()); + if 0 == left_time { + break; + } + match TcpStream::connect_timeout(&addr, Duration::from_nanos(left_time).min(SLICE)) { + Ok(l) => return Ok(l), + Err(e) => last_err = Some(e), + } + } + } + Err(last_err.unwrap_or_else(|| { + Error::new( + ErrorKind::InvalidInput, + "could not resolve to any addresses", + ) + })) } #[cfg(test)] mod tests { - use super::*; + use crate::{init, shutdown}; + use open_coroutine_core::net::config::Config; #[test] - fn test_link() { - init(Config::default()); + fn test() { + init(Config::single()); + let join = task!( + |_| { + println!("Hello, world!"); + }, + (), + ); + assert_eq!(Some(()), join.join().expect("join failed")); + shutdown(); } } diff --git a/open-coroutine/src/task.rs b/open-coroutine/src/task.rs deleted file mode 100644 index 9a1364e6..00000000 --- a/open-coroutine/src/task.rs +++ /dev/null @@ -1,139 +0,0 @@ -use open_coroutine_core::coroutine::suspender::Suspender; -use open_coroutine_core::net::event_loop::core::EventLoop; -use open_coroutine_core::net::event_loop::UserFunc; -use std::cmp::Ordering; -use std::ffi::{c_char, c_void}; -use std::io::{Error, ErrorKind}; -use std::time::Duration; - -#[allow(improper_ctypes)] -extern "C" { - fn task_crate(f: UserFunc, param: usize) -> JoinHandle; - - fn task_join(handle: JoinHandle) -> libc::c_long; - - fn task_timeout_join(handle: &JoinHandle, ns_time: u64) -> libc::c_long; -} - -pub fn task(f: F, param: P) -> JoinHandle -where - F: FnOnce(*const Suspender<(), ()>, P) -> R + Copy, -{ - extern "C" fn co_main( - suspender: *const Suspender<(), ()>, - input: usize, - ) -> usize - where - F: FnOnce(*const Suspender<(), ()>, P) -> R + Copy, - { - unsafe { - let ptr = &mut *((input as *mut c_void).cast::<(F, P)>()); - let data = std::ptr::read_unaligned(ptr); - let result: &'static mut R = Box::leak(Box::new((data.0)(suspender, data.1))); - std::ptr::from_mut::(result).cast::() as usize - } - } - let inner = Box::leak(Box::new((f, param))); - unsafe { - task_crate( - co_main::, - std::ptr::from_mut::<(F, P)>(inner).cast::() as usize, - ) - } -} - -#[macro_export] -macro_rules! task { - ( $f: expr , $param:expr $(,)? ) => { - $crate::task::task($f, $param) - }; -} - -#[repr(C)] -#[derive(Debug)] -pub struct JoinHandle(*const EventLoop, *const c_char); - -impl JoinHandle { - #[allow(clippy::cast_possible_truncation)] - pub fn timeout_join(&self, dur: Duration) -> std::io::Result> { - unsafe { - let ptr = task_timeout_join(self, dur.as_nanos() as u64); - match ptr.cmp(&0) { - Ordering::Less => Err(Error::new(ErrorKind::Other, "timeout join failed")), - Ordering::Equal => Ok(None), - Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), - } - } - } - - pub fn join(self) -> std::io::Result> { - unsafe { - let ptr = task_join(self); - match ptr.cmp(&0) { - Ordering::Less => Err(Error::new(ErrorKind::Other, "join failed")), - Ordering::Equal => Ok(None), - Ordering::Greater => Ok(Some(std::ptr::read_unaligned(ptr as *mut R))), - } - } - } -} - -#[cfg(all(test, not(windows)))] -mod tests { - use std::sync::{Arc, Condvar, Mutex}; - use std::time::Duration; - - #[test] - fn task_simplest() -> std::io::Result<()> { - let pair = Arc::new((Mutex::new(true), Condvar::new())); - let pair2 = Arc::clone(&pair); - let handler = std::thread::Builder::new() - .name("test_join".to_string()) - .spawn(move || { - let handler1 = task!( - |_, input| { - println!("[task1] launched with {}", input); - input - }, - 1, - ); - let handler2 = task!( - |_, input| { - println!("[task2] launched with {}", input); - input - }, - "hello", - ); - unsafe { - assert_eq!(1, handler1.join().unwrap().unwrap()); - assert_eq!("hello", &*handler2.join::<*mut str>().unwrap().unwrap()); - } - - let (lock, cvar) = &*pair2; - let mut pending = lock.lock().unwrap(); - *pending = false; - // notify the condvar that the value has changed. - cvar.notify_one(); - }) - .expect("failed to spawn thread"); - - // wait for the thread to start up - let (lock, cvar) = &*pair; - let result = cvar - .wait_timeout_while( - lock.lock().unwrap(), - Duration::from_millis(3000), - |&mut pending| pending, - ) - .unwrap(); - if result.1.timed_out() { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "join failed", - )) - } else { - handler.join().unwrap(); - Ok(()) - } - } -} diff --git a/open-coroutine/tests/file_co.rs b/open-coroutine/tests/file_co.rs new file mode 100644 index 00000000..13f5f3a6 --- /dev/null +++ b/open-coroutine/tests/file_co.rs @@ -0,0 +1,8 @@ +include!("../examples/file_co.rs"); + +// The implementation of rust std is inconsistent between unix and windows. +#[cfg(not(windows))] +#[test] +fn file_co() -> Result<()> { + main() +} diff --git a/open-coroutine/tests/file_not_co.rs b/open-coroutine/tests/file_not_co.rs new file mode 100644 index 00000000..d87ea98b --- /dev/null +++ b/open-coroutine/tests/file_not_co.rs @@ -0,0 +1,8 @@ +include!("../examples/file_not_co.rs"); + +// The implementation of rust std is inconsistent between unix and windows. +#[cfg(not(windows))] +#[test] +fn file_not_co() -> std::io::Result<()> { + main() +} diff --git a/open-coroutine/tests/scalable_stack.rs b/open-coroutine/tests/scalable_stack.rs new file mode 100644 index 00000000..28ad7cc0 --- /dev/null +++ b/open-coroutine/tests/scalable_stack.rs @@ -0,0 +1,6 @@ +include!("../examples/scalable_stack.rs"); + +#[test] +fn scalable_stack() { + main(); +} diff --git a/open-coroutine/tests/sleep_co.rs b/open-coroutine/tests/sleep_co.rs new file mode 100644 index 00000000..475aea96 --- /dev/null +++ b/open-coroutine/tests/sleep_co.rs @@ -0,0 +1,6 @@ +include!("../examples/sleep_co.rs"); + +#[test] +fn sleep_co() { + main(); +} diff --git a/open-coroutine/tests/sleep_not_co.rs b/open-coroutine/tests/sleep_not_co.rs new file mode 100644 index 00000000..58b46181 --- /dev/null +++ b/open-coroutine/tests/sleep_not_co.rs @@ -0,0 +1,6 @@ +include!("../examples/sleep_not_co.rs"); + +#[test] +fn sleep_not_co() { + main(); +} diff --git a/open-coroutine/tests/socket_co.rs b/open-coroutine/tests/socket_co.rs new file mode 100644 index 00000000..50c7a9fc --- /dev/null +++ b/open-coroutine/tests/socket_co.rs @@ -0,0 +1,6 @@ +include!("../examples/socket_co.rs"); + +#[test] +fn socket_co() -> std::io::Result<()> { + main() +} diff --git a/open-coroutine/tests/socket_co_client.rs b/open-coroutine/tests/socket_co_client.rs new file mode 100644 index 00000000..e135fbf3 --- /dev/null +++ b/open-coroutine/tests/socket_co_client.rs @@ -0,0 +1,6 @@ +include!("../examples/socket_co_client.rs"); + +#[test] +fn socket_co_client() -> std::io::Result<()> { + main() +} diff --git a/open-coroutine/tests/socket_co_server.rs b/open-coroutine/tests/socket_co_server.rs new file mode 100644 index 00000000..26fc42b0 --- /dev/null +++ b/open-coroutine/tests/socket_co_server.rs @@ -0,0 +1,6 @@ +include!("../examples/socket_co_server.rs"); + +#[test] +fn socket_co_server() -> std::io::Result<()> { + main() +} diff --git a/open-coroutine/tests/socket_not_co.rs b/open-coroutine/tests/socket_not_co.rs new file mode 100644 index 00000000..bd623f10 --- /dev/null +++ b/open-coroutine/tests/socket_not_co.rs @@ -0,0 +1,6 @@ +include!("../examples/socket_not_co.rs"); + +#[test] +fn socket_not_co() -> std::io::Result<()> { + main() +} diff --git a/publish.sh b/publish.sh index 14f8990e..b5b090b6 100644 --- a/publish.sh +++ b/publish.sh @@ -1,28 +1,13 @@ #!/bin/bash -cd open-coroutine-timer -cargo publish +cd core +cargo publish --registry crates-io -cd .. -cd open-coroutine-queue -cargo publish +cd ../hook +cargo publish --registry crates-io -cd .. -cd open-coroutine-iouring -cargo publish +cd ../macros +cargo publish --registry crates-io -cd .. -cd open-coroutine-core -cargo publish - -cd .. -cd open-coroutine-hooks -cargo publish - -cd .. -cd open-coroutine-macros -cargo publish - -cd .. -cd open-coroutine -cargo publish +cd ../open-coroutine +cargo publish --registry crates-io