Update async simple (#282)
* [third_party] Update async_simple --------- Co-authored-by: PikachuHy <pikachuhy@linux.alibaba.com>
This commit is contained in:
parent
a25474a63a
commit
924b07bdc8
|
@ -52,4 +52,5 @@ node_modules
|
|||
|
||||
# Bazel
|
||||
bazel-*
|
||||
!BUILD.bazel
|
||||
!BUILD.bazel
|
||||
cdb.json
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
---
|
||||
Language: JavaScript
|
||||
DisableFormat: true
|
||||
---
|
||||
Language: Json
|
||||
DisableFormat: true
|
|
@ -0,0 +1,17 @@
|
|||
### Search before asking
|
||||
|
||||
- [ ] I searched the [issues](https://github.com/alibaba/async_simple/issues) and found no similar issues.
|
||||
|
||||
|
||||
### What happened + What you expected to happen
|
||||
|
||||
|
||||
### Reproduction way
|
||||
|
||||
|
||||
### Anything else
|
||||
|
||||
|
||||
### Are you willing to submit a PR?
|
||||
|
||||
- [ ] Yes I am willing to submit a PR!
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Thank you for your contribution! -->
|
||||
|
||||
## Why
|
||||
|
||||
<!-- For example: "Closes #1234" -->
|
||||
|
||||
<!-- Please give a short summary of the change and the problem this solves. -->
|
||||
|
||||
## What is changing
|
||||
|
||||
## Example
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
name: Performance for commit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Performance regression check
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build benchmark
|
||||
run: |
|
||||
sudo apt install libaio-dev libgtest-dev -y
|
||||
cd /usr/src/gtest
|
||||
sudo mkdir build && cd build
|
||||
sudo cmake .. && sudo make
|
||||
sudo apt install libgmock-dev -y
|
||||
sudo apt install libbenchmark-dev -y
|
||||
cd ${{github.workspace}} && CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
cd ${{github.workspace}}/build/benchmarks && make -j
|
||||
- name: Run benchmark
|
||||
run: cd ${{github.workspace}}/build/benchmarks && ./benchmarking --benchmark_format=json | tee ./benchmarking.json
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: C++ Benchmark
|
||||
tool: 'googlecpp'
|
||||
output-file-path: ${{github.workspace}}/build/benchmarks/benchmarking.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-always: true
|
||||
# Show alert with commit comment on detecting possible performance regression
|
||||
alert-threshold: '150%'
|
||||
fail-threshold: '200%'
|
||||
gh-pages-branch: benchmark-monitoring
|
||||
auto-push: true
|
||||
benchmark-data-dir-path: benchmark-monitoring
|
||||
alert-comment-cc-users: '@ChuanqiXu9, @RainMark, @qicosmos, @forever-hy'
|
|
@ -0,0 +1,44 @@
|
|||
name: Performance for Pull Request
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
name: Performance regression check
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build benchmark
|
||||
run: |
|
||||
sudo apt install libaio-dev libgtest-dev -y
|
||||
cd /usr/src/gtest
|
||||
sudo mkdir build && cd build
|
||||
sudo cmake .. && sudo make
|
||||
sudo apt install libgmock-dev -y
|
||||
sudo apt install libbenchmark-dev -y
|
||||
cd ${{github.workspace}} && CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
cd ${{github.workspace}}/build/benchmarks && make -j
|
||||
- name: Run benchmark
|
||||
run: cd ${{github.workspace}}/build/benchmarks && ./benchmarking --benchmark_format=json | tee benchmarking.json
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: C++ Benchmark
|
||||
tool: 'googlecpp'
|
||||
output-file-path: ${{github.workspace}}/build/benchmarks/benchmarking.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-always: false
|
||||
# Show alert with commit comment on detecting possible performance regression
|
||||
alert-threshold: '150%'
|
||||
fail-threshold: '200%'
|
||||
comment-on-alert: true
|
||||
auto-push: false
|
||||
gh-pages-branch: benchmark-monitoring
|
||||
benchmark-data-dir-path: benchmark-monitoring
|
|
@ -0,0 +1,28 @@
|
|||
name: Bazel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: deps
|
||||
# Install Bazel See: https://bazel.build/install/ubuntu
|
||||
run: |
|
||||
sudo apt install -y libaio-dev
|
||||
- name: Build
|
||||
# Build your program with clang
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel build --action_env=CXX=clang++ --action_env=CC=clang ...
|
||||
|
||||
- name: Test
|
||||
# Build and Execute tests
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel test --action_env=CXX=clang++ --action_env=CC=clang --test_output=errors ...
|
|
@ -0,0 +1,30 @@
|
|||
name: Clang Format Diff
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: install clang-format
|
||||
run: |
|
||||
sudo apt install -y clang-format
|
||||
clang-format --version
|
||||
- name: check-diff
|
||||
run: |
|
||||
diff=`git-clang-format --diff HEAD^`
|
||||
if ! [[ "$diff" = "no modified files to format" || "$diff" = "clang-format did not modify any files" ]]; then
|
||||
echo "The diff you sent is not formatted correctly."
|
||||
echo "The suggested format is"
|
||||
echo "$diff"
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,48 @@
|
|||
name: CMake
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
mode: [Release, Debug]
|
||||
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: deps
|
||||
run: |
|
||||
sudo apt install libaio-dev libgtest-dev -y
|
||||
cd /usr/src/gtest
|
||||
sudo mkdir build && cd build
|
||||
sudo cmake .. && sudo make
|
||||
sudo apt install libgmock-dev -y
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.mode}}
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} -j --verbose
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
run: ctest -C ${{matrix.mode}} --output-on-failure
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
name: CMake_gcc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build_with_gcc:
|
||||
strategy:
|
||||
matrix:
|
||||
gcc_version: [10, 11, 12]
|
||||
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: deps
|
||||
run: |
|
||||
sudo apt install -y build-essential
|
||||
sudo apt install -y gcc-${{matrix.gcc_version}} g++-${{matrix.gcc_version}}
|
||||
sudo apt install libaio-dev libgtest-dev libgmock-dev -y
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: CXX=g++-${{matrix.gcc_version}} CC=gcc-${{matrix.gcc_version}} cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j --verbose
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure
|
|
@ -0,0 +1,52 @@
|
|||
name: Conan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: deps
|
||||
run: |
|
||||
sudo apt install libaio-dev libgtest-dev -y
|
||||
cd /usr/src/gtest
|
||||
sudo mkdir build && cd build
|
||||
sudo cmake .. && sudo make
|
||||
sudo apt install libgmock-dev -y
|
||||
- name: Install Conan
|
||||
id: conan
|
||||
uses: turtlebrowser/get-conan@main
|
||||
|
||||
- name: Conan version
|
||||
run: echo "${{ steps.conan.outputs.version }}"
|
||||
|
||||
- name: Generate conan profile
|
||||
run: CXX=clang++ CC=clang conan profile detect --name clang_tmp
|
||||
|
||||
- name: Install deps
|
||||
run: conan install ${{github.workspace}} -pr:b=clang_tmp -pr=clang_tmp -o header_only=False
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: CXX=clang++ CC=clang conan build ${{github.workspace}} -pr:b=clang_tmp -pr=clang_tmp -o header_only=False
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/build/${{env.BUILD_TYPE}}
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
name: macosx
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build_with_bazel:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
# Build all program
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel build --define=ASYNC_SIMPLE_DISABLE_AIO=true ...
|
||||
|
||||
- name: Test
|
||||
# Execute tests
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel test --define=ASYNC_SIMPLE_DISABLE_AIO=true --test_output=errors ...
|
||||
build_with_cmake:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: deps
|
||||
run: brew install googletest
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --verbose -j
|
||||
|
||||
- name: Test
|
||||
# Execute tests defined by the CMake configuration.
|
||||
working-directory: ${{github.workspace}}/build
|
||||
run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure
|
|
@ -0,0 +1,50 @@
|
|||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy static content to Pages
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install
|
||||
run: sudo apt-get install doxygen graphviz
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
- name: Yarn Install
|
||||
run: yarn install
|
||||
working-directory: ${{github.workspace}}
|
||||
- name: Build Website
|
||||
run: yarn docs:build
|
||||
working-directory: ${{github.workspace}}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: 'docs/public'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
|
@ -0,0 +1,60 @@
|
|||
name: Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build_with_cmake:
|
||||
runs-on: windows-2019
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
mode: [ Release ]
|
||||
arch: [ x86 ]
|
||||
|
||||
env:
|
||||
CXX: cl.exe
|
||||
CC: cl.exe
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Generate Project
|
||||
run: cmake -B Build/${{ matrix.mode }} -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DRFK_DEV=1 -G "Visual Studio 16 2019" -A x64
|
||||
|
||||
- name: Build async_simple
|
||||
run: cmake --build Build/${{ matrix.mode }} --config ${{ matrix.mode }} --verbose
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}/Build/${{ matrix.mode }}
|
||||
# Execute tests defined by the CMake configuration.
|
||||
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||
run: ctest -C ${{ matrix.mode }} --output-on-failure
|
||||
|
||||
build_with_bazel:
|
||||
runs-on: windows-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
mode: [ Release ]
|
||||
arch: [ x86 ]
|
||||
env:
|
||||
CXX: cl.exe
|
||||
CC: cl.exe
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel build --cxxopt=/std:c++20 ...
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}
|
||||
run: bazel test --cxxopt=/std:c++20 --test_output=errors ...
|
|
@ -0,0 +1,34 @@
|
|||
*.pyc
|
||||
*.os
|
||||
*~
|
||||
build_options.*
|
||||
\.*
|
||||
config.log
|
||||
*.o
|
||||
build/
|
||||
build*
|
||||
_external/
|
||||
core.*
|
||||
\#*\#
|
||||
!.github
|
||||
!.gitignore
|
||||
!.clang-format
|
||||
*.so
|
||||
*.a
|
||||
vgcore.*
|
||||
indexlib/config.h
|
||||
indexlib/config.cpp
|
||||
data
|
||||
testdata/*Test/
|
||||
testdata/*TestE/
|
||||
error_log_collector.log
|
||||
coro_trace.log
|
||||
*.log
|
||||
test.h
|
||||
bazel-*
|
||||
!.bazelrc
|
||||
cmake-build-debug
|
||||
!docs/.vitepress
|
||||
docs/.vitepress/cache
|
||||
docs/public
|
||||
node_modules
|
|
@ -1,19 +1,12 @@
|
|||
file(GLOB coro_src "coro/*.cpp")
|
||||
file(GLOB executors_src "executors/*.cpp")
|
||||
|
||||
if(UTHREAD)
|
||||
if(${UTHREAD})
|
||||
file(GLOB uthread_src "uthread/internal/*.cc")
|
||||
# Compiler-rt15 enables new sanitizer option sanitize-address-use-after-return,
|
||||
# But it can't take user thread well. So disable it for thread.cc as a workaround.
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "13")
|
||||
set_property(SOURCE ${uthread_src} PROPERTY COMPILE_FLAGS -fsanitize-address-use-after-return=never)
|
||||
endif()
|
||||
if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
|
||||
file(GLOB uthread_asm_src "uthread/internal/*arm64_aapcs_elf*")
|
||||
elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64")
|
||||
file(GLOB uthread_asm_src "uthread/internal/*x86_64_sysv_elf*")
|
||||
endif()
|
||||
file(GLOB uthread_asm_src "uthread/internal/${CMAKE_SYSTEM_NAME}/${CMAKE_SYSTEM_PROCESSOR}/*.S")
|
||||
endif()
|
||||
|
||||
file(GLOB headers "*.h")
|
||||
|
@ -52,6 +45,10 @@ else()
|
|||
install(TARGETS async_simple DESTINATION lib/)
|
||||
endif()
|
||||
|
||||
set_target_properties(async_simple PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
install(FILES ${headers} DESTINATION include/async_simple)
|
||||
install(FILES ${coro_header} DESTINATION include/async_simple/coro)
|
||||
install(FILES ${executors_header} DESTINATION include/async_simple/executors)
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
#ifndef ASYNC_SIMPLE_COLLECT_H
|
||||
#define ASYNC_SIMPLE_COLLECT_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <exception>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Future.h"
|
||||
#include "async_simple/Try.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
@ -46,11 +46,11 @@ namespace async_simple {
|
|||
//
|
||||
// Since the returned type is a future. So the user wants to get its value
|
||||
// could use `get()` method synchronously or `then*()` method asynchronously.
|
||||
template <typename Iter>
|
||||
template <std::input_iterator Iterator>
|
||||
inline Future<std::vector<
|
||||
Try<typename std::iterator_traits<Iter>::value_type::value_type>>>
|
||||
collectAll(Iter begin, Iter end) {
|
||||
using T = typename std::iterator_traits<Iter>::value_type::value_type;
|
||||
Try<typename std::iterator_traits<Iterator>::value_type::value_type>>>
|
||||
collectAll(Iterator begin, Iterator end) {
|
||||
using T = typename std::iterator_traits<Iterator>::value_type::value_type;
|
||||
size_t n = std::distance(begin, end);
|
||||
|
||||
bool allReady = true;
|
||||
|
@ -81,12 +81,11 @@ collectAll(Iter begin, Iter end) {
|
|||
};
|
||||
|
||||
auto ctx = std::make_shared<Context>(n, std::move(promise));
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto cur = begin + i;
|
||||
if (cur->hasResult()) {
|
||||
ctx->results[i] = std::move(cur->result());
|
||||
for (size_t i = 0; i < n; ++i, ++begin) {
|
||||
if (begin->hasResult()) {
|
||||
ctx->results[i] = std::move(begin->result());
|
||||
} else {
|
||||
cur->setContinuation([ctx, i](Try<T>&& t) mutable {
|
||||
begin->setContinuation([ctx, i](Try<T>&& t) mutable {
|
||||
ctx->results[i] = std::move(t);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,6 +32,18 @@
|
|||
#define AS_INLINE __attribute__((__always_inline__)) inline
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define AS_INTERNAL_USE_ASAN 1
|
||||
#endif // __has_feature(address_sanitizer)
|
||||
#endif // __clang__
|
||||
|
||||
#ifdef __GNUC__
|
||||
#ifdef __SANITIZE_ADDRESS__ // GCC
|
||||
#define AS_INTERNAL_USE_ASAN 1
|
||||
#endif // __SANITIZE_ADDRESS__
|
||||
#endif // __GNUC__
|
||||
|
||||
namespace async_simple {
|
||||
// Different from assert, logicAssert is meaningful in
|
||||
// release mode. logicAssert should be used in case that
|
||||
|
|
|
@ -16,28 +16,27 @@
|
|||
#ifndef ASYNC_SIMPLE_EXECUTOR_H
|
||||
#define ASYNC_SIMPLE_EXECUTOR_H
|
||||
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
// Stat information for an executor.
|
||||
// It contains the number of pending task
|
||||
// for the executor now.
|
||||
struct ExecutorStat {
|
||||
size_t pendingTaskCount = 0;
|
||||
ExecutorStat() = default;
|
||||
size_t pendingTaskCount = 0;
|
||||
ExecutorStat() = default;
|
||||
};
|
||||
// Options for a schedule.
|
||||
// The option contains:
|
||||
// - bool prompt. Whether or not this schedule
|
||||
// should be prompted.
|
||||
struct ScheduleOptions {
|
||||
bool prompt = true;
|
||||
ScheduleOptions() = default;
|
||||
bool prompt = true;
|
||||
ScheduleOptions() = default;
|
||||
};
|
||||
|
||||
// Awaitable to get the current executor.
|
||||
|
@ -61,115 +60,115 @@ struct CurrentExecutor {};
|
|||
class IOExecutor;
|
||||
|
||||
class Executor {
|
||||
public:
|
||||
// Context is an identification for the context where an executor
|
||||
// should run. See checkin/checkout for details.
|
||||
using Context = void *;
|
||||
static constexpr Context NULLCTX = nullptr;
|
||||
public:
|
||||
// Context is an identification for the context where an executor
|
||||
// should run. See checkin/checkout for details.
|
||||
using Context = void *;
|
||||
static constexpr Context NULLCTX = nullptr;
|
||||
|
||||
// A time duration in microseconds.
|
||||
using Duration = std::chrono::duration<int64_t, std::micro>;
|
||||
// A time duration in microseconds.
|
||||
using Duration = std::chrono::duration<int64_t, std::micro>;
|
||||
|
||||
// The schedulable function. Func should accept no argument and
|
||||
// return void.
|
||||
using Func = std::function<void()>;
|
||||
class TimeAwaitable;
|
||||
class TimeAwaiter;
|
||||
// The schedulable function. Func should accept no argument and
|
||||
// return void.
|
||||
using Func = std::function<void()>;
|
||||
class TimeAwaitable;
|
||||
class TimeAwaiter;
|
||||
|
||||
Executor(std::string name = "default") : _name(std::move(name)) {}
|
||||
virtual ~Executor() {}
|
||||
Executor(std::string name = "default") : _name(std::move(name)) {}
|
||||
virtual ~Executor() {}
|
||||
|
||||
Executor(const Executor &) = delete;
|
||||
Executor &operator=(const Executor &) = delete;
|
||||
Executor(const Executor &) = delete;
|
||||
Executor &operator=(const Executor &) = delete;
|
||||
|
||||
// Schedule a function.
|
||||
// `schedule` would return false if schedule failed, which means function
|
||||
// func will not be executed. In case schedule return true, the executor
|
||||
// should guarantee that the func would be executed.
|
||||
virtual bool schedule(Func func) = 0;
|
||||
// Return true if caller runs in the executor.
|
||||
virtual bool currentThreadInExecutor() const {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
virtual ExecutorStat stat() const {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
// Schedule a function.
|
||||
// `schedule` would return false if schedule failed, which means function
|
||||
// func will not be executed. In case schedule return true, the executor
|
||||
// should guarantee that the func would be executed.
|
||||
virtual bool schedule(Func func) = 0;
|
||||
// Return true if caller runs in the executor.
|
||||
virtual bool currentThreadInExecutor() const {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
virtual ExecutorStat stat() const {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
|
||||
// checkout() return current "Context", which defined by executor
|
||||
// implementation, then checkin(func, "Context") should schedule func to the
|
||||
// same "Context" as before.
|
||||
virtual size_t currentContextId() const { return 0; };
|
||||
virtual Context checkout() { return NULLCTX; }
|
||||
virtual bool checkin(Func func, [[maybe_unused]] Context ctx,
|
||||
[[maybe_unused]] ScheduleOptions opts) {
|
||||
return schedule(std::move(func));
|
||||
}
|
||||
virtual bool checkin(Func func, Context ctx) {
|
||||
static ScheduleOptions opts;
|
||||
return checkin(std::move(func), ctx, opts);
|
||||
}
|
||||
// checkout() return current "Context", which defined by executor
|
||||
// implementation, then checkin(func, "Context") should schedule func to the
|
||||
// same "Context" as before.
|
||||
virtual size_t currentContextId() const { return 0; };
|
||||
virtual Context checkout() { return NULLCTX; }
|
||||
virtual bool checkin(Func func, [[maybe_unused]] Context ctx,
|
||||
[[maybe_unused]] ScheduleOptions opts) {
|
||||
return schedule(std::move(func));
|
||||
}
|
||||
virtual bool checkin(Func func, Context ctx) {
|
||||
static ScheduleOptions opts;
|
||||
return checkin(std::move(func), ctx, opts);
|
||||
}
|
||||
|
||||
const std::string &name() const { return _name; }
|
||||
const std::string &name() const { return _name; }
|
||||
|
||||
// Use
|
||||
// co_await executor.after(sometime)
|
||||
// to schedule current execution after some time.
|
||||
TimeAwaitable after(Duration dur);
|
||||
// Use
|
||||
// co_await executor.after(sometime)
|
||||
// to schedule current execution after some time.
|
||||
TimeAwaitable after(Duration dur);
|
||||
|
||||
// IOExecutor accepts IO read/write requests.
|
||||
// Return nullptr if the executor doesn't offer an IOExecutor.
|
||||
virtual IOExecutor *getIOExecutor() {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
// IOExecutor accepts IO read/write requests.
|
||||
// Return nullptr if the executor doesn't offer an IOExecutor.
|
||||
virtual IOExecutor *getIOExecutor() {
|
||||
throw std::logic_error("Not implemented");
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void schedule(Func func, Duration dur) {
|
||||
std::thread([this, func = std::move(func), dur]() {
|
||||
std::this_thread::sleep_for(dur);
|
||||
schedule(std::move(func));
|
||||
}).detach();
|
||||
}
|
||||
protected:
|
||||
virtual void schedule(Func func, Duration dur) {
|
||||
std::thread([this, func = std::move(func), dur]() {
|
||||
std::this_thread::sleep_for(dur);
|
||||
schedule(std::move(func));
|
||||
}).detach();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
private:
|
||||
std::string _name;
|
||||
};
|
||||
|
||||
// Awaiter to implement Executor::after.
|
||||
class Executor::TimeAwaiter {
|
||||
public:
|
||||
TimeAwaiter(Executor *ex, Executor::Duration dur) : _ex(ex), _dur(dur) {}
|
||||
public:
|
||||
TimeAwaiter(Executor *ex, Executor::Duration dur) : _ex(ex), _dur(dur) {}
|
||||
|
||||
public:
|
||||
bool await_ready() const noexcept { return false; }
|
||||
public:
|
||||
bool await_ready() const noexcept { return false; }
|
||||
|
||||
template <typename PromiseType>
|
||||
void await_suspend(std::coroutine_handle<PromiseType> continuation) {
|
||||
std::function<void()> func = [c = continuation]() mutable {
|
||||
c.resume();
|
||||
};
|
||||
_ex->schedule(func, _dur);
|
||||
}
|
||||
void await_resume() const noexcept {}
|
||||
template <typename PromiseType>
|
||||
void await_suspend(std::coroutine_handle<PromiseType> continuation) {
|
||||
std::function<void()> func = [c = continuation]() mutable {
|
||||
c.resume();
|
||||
};
|
||||
_ex->schedule(func, _dur);
|
||||
}
|
||||
void await_resume() const noexcept {}
|
||||
|
||||
private:
|
||||
Executor *_ex;
|
||||
Executor::Duration _dur;
|
||||
private:
|
||||
Executor *_ex;
|
||||
Executor::Duration _dur;
|
||||
};
|
||||
|
||||
// Awaitable to implement Executor::after.
|
||||
class Executor::TimeAwaitable {
|
||||
public:
|
||||
TimeAwaitable(Executor *ex, Executor::Duration dur) : _ex(ex), _dur(dur) {}
|
||||
public:
|
||||
TimeAwaitable(Executor *ex, Executor::Duration dur) : _ex(ex), _dur(dur) {}
|
||||
|
||||
auto coAwait(Executor *) { return Executor::TimeAwaiter(_ex, _dur); }
|
||||
auto coAwait(Executor *) { return Executor::TimeAwaiter(_ex, _dur); }
|
||||
|
||||
private:
|
||||
Executor *_ex;
|
||||
Executor::Duration _dur;
|
||||
private:
|
||||
Executor *_ex;
|
||||
Executor::Duration _dur;
|
||||
};
|
||||
|
||||
Executor::TimeAwaitable inline Executor::after(Executor::Duration dur) {
|
||||
return Executor::TimeAwaitable(this, dur);
|
||||
return Executor::TimeAwaitable(this, dur);
|
||||
};
|
||||
|
||||
} // namespace async_simple
|
||||
|
|
|
@ -16,15 +16,18 @@
|
|||
#ifndef ASYNC_SIMPLE_FUTURE_H
|
||||
#define ASYNC_SIMPLE_FUTURE_H
|
||||
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/FutureState.h>
|
||||
#include <async_simple/LocalState.h>
|
||||
#include <async_simple/Promise.h>
|
||||
#include <async_simple/Traits.h>
|
||||
#include <type_traits>
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/FutureState.h"
|
||||
#include "async_simple/LocalState.h"
|
||||
#include "async_simple/Promise.h"
|
||||
#include "async_simple/Traits.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
template <typename T>
|
||||
class Promise;
|
||||
|
||||
// The well-known Future/Promise pairs mimic a producer/consumer pair.
|
||||
// The Future stands for the consumer-side.
|
||||
//
|
||||
|
@ -45,14 +48,23 @@ namespace async_simple {
|
|||
// he should call makeReadyFuture().
|
||||
template <typename T>
|
||||
class Future {
|
||||
private:
|
||||
// If T is void, the inner_value_type is Unit. It will be used by
|
||||
// `FutureState` and `LocalState`. Because `Try<void>` cannot distinguish
|
||||
// between `Nothing` state and `Value` state.
|
||||
// It maybe remove Unit after next version, and then will change the
|
||||
// `Try<void>` to distinguish between `Nothing` state and `Value` state
|
||||
using inner_value_type = std::conditional_t<std::is_void_v<T>, Unit, T>;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
Future(FutureState<T>* fs) : _sharedState(fs) {
|
||||
Future(FutureState<inner_value_type>* fs) : _sharedState(fs) {
|
||||
if (_sharedState) {
|
||||
_sharedState->attachOne();
|
||||
}
|
||||
}
|
||||
Future(Try<T>&& t) : _sharedState(nullptr), _localState(std::move(t)) {}
|
||||
Future(Try<inner_value_type>&& t)
|
||||
: _sharedState(nullptr), _localState(std::move(t)) {}
|
||||
|
||||
~Future() {
|
||||
if (_sharedState) {
|
||||
|
@ -86,13 +98,31 @@ public:
|
|||
return _localState.hasResult() || _sharedState->hasResult();
|
||||
}
|
||||
|
||||
T&& value() && { return std::move(result().value()); }
|
||||
T& value() & { return result().value(); }
|
||||
const T& value() const& { return result().value(); }
|
||||
std::add_rvalue_reference_t<T> value() && {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
return result().value();
|
||||
} else {
|
||||
return std::move(result().value());
|
||||
}
|
||||
}
|
||||
std::add_lvalue_reference_t<T> value() & { return result().value(); }
|
||||
const std::add_lvalue_reference_t<T> value() const& {
|
||||
return result().value();
|
||||
}
|
||||
|
||||
Try<T>&& result() && { return std::move(getTry(*this)); }
|
||||
Try<T>& result() & { return getTry(*this); }
|
||||
const Try<T>& result() const& { return getTry(*this); }
|
||||
Try<T>&& result() && requires(!std::is_void_v<T>) {
|
||||
return std::move(getTry(*this));
|
||||
}
|
||||
Try<T>& result() & requires(!std::is_void_v<T>) { return getTry(*this); }
|
||||
const Try<T>& result() const& requires(!std::is_void_v<T>) {
|
||||
return getTry(*this);
|
||||
}
|
||||
|
||||
Try<void> result() && requires(std::is_void_v<T>) { return getTry(*this); }
|
||||
Try<void> result() & requires(std::is_void_v<T>) { return getTry(*this); }
|
||||
Try<void> result() const& requires(std::is_void_v<T>) {
|
||||
return getTry(*this);
|
||||
}
|
||||
|
||||
// get is only allowed on rvalue, aka, Future is not valid after get
|
||||
// invoked.
|
||||
|
@ -161,12 +191,29 @@ public:
|
|||
template <typename F, typename R = ValueCallableResult<T, F>>
|
||||
Future<typename R::ReturnsFuture::Inner> thenValue(F&& f) && {
|
||||
auto lambda = [func = std::forward<F>(f)](Try<T>&& t) mutable {
|
||||
return std::forward<F>(func)(std::move(t).value());
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
t.value();
|
||||
return std::forward<F>(func)();
|
||||
} else {
|
||||
return std::forward<F>(func)(std::move(t).value());
|
||||
};
|
||||
};
|
||||
using Func = decltype(lambda);
|
||||
return thenImpl<Func, TryCallableResult<T, Func>>(std::move(lambda));
|
||||
}
|
||||
|
||||
template <typename F,
|
||||
typename R = std::conditional_t<std::is_invocable_v<F, T>,
|
||||
ValueCallableResult<T, F>,
|
||||
TryCallableResult<T, F>>>
|
||||
Future<typename R::ReturnsFuture::Inner> then(F&& f) && {
|
||||
if constexpr (std::is_invocable_v<F, T>) {
|
||||
return std::move(*this).thenValue(std::forward<F>(f));
|
||||
} else {
|
||||
return std::move(*this).thenTry(std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// This section is public because they may invoked by other type of Future.
|
||||
// They are not suppose to be public.
|
||||
|
@ -224,22 +271,27 @@ private:
|
|||
|
||||
// continuation returns a future
|
||||
template <typename F, typename R>
|
||||
std::enable_if_t<R::ReturnsFuture::value,
|
||||
Future<typename R::ReturnsFuture::Inner>>
|
||||
thenImpl(F&& func) {
|
||||
Future<typename R::ReturnsFuture::Inner> thenImpl(F&& func) {
|
||||
logicAssert(valid(), "Future is broken");
|
||||
using T2 = typename R::ReturnsFuture::Inner;
|
||||
|
||||
if (!_sharedState) {
|
||||
try {
|
||||
auto newFuture =
|
||||
std::forward<F>(func)(std::move(_localState.getTry()));
|
||||
if (!newFuture.getExecutor()) {
|
||||
newFuture.setExecutor(_localState.getExecutor());
|
||||
if constexpr (R::ReturnsFuture::value) {
|
||||
try {
|
||||
auto newFuture =
|
||||
std::forward<F>(func)(std::move(_localState.getTry()));
|
||||
if (!newFuture.getExecutor()) {
|
||||
newFuture.setExecutor(_localState.getExecutor());
|
||||
}
|
||||
return newFuture;
|
||||
} catch (...) {
|
||||
return Future<T2>(Try<T2>(std::current_exception()));
|
||||
}
|
||||
} else {
|
||||
Future<T2> newFuture(makeTryCall(
|
||||
std::forward<F>(func), std::move(_localState.getTry())));
|
||||
newFuture.setExecutor(_localState.getExecutor());
|
||||
return newFuture;
|
||||
} catch (...) {
|
||||
return Future<T2>(Try<T2>(std::current_exception()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,54 +304,30 @@ private:
|
|||
if (!R::isTry && t.hasError()) {
|
||||
p.setException(t.getException());
|
||||
} else {
|
||||
try {
|
||||
auto f2 = f(std::move(t));
|
||||
f2.setContinuation(
|
||||
[pm = std::move(p)](Try<T2>&& t2) mutable {
|
||||
pm.setValue(std::move(t2));
|
||||
});
|
||||
} catch (...) {
|
||||
p.setException(std::current_exception());
|
||||
if constexpr (R::ReturnsFuture::value) {
|
||||
try {
|
||||
auto f2 = f(std::move(t));
|
||||
f2.setContinuation(
|
||||
[pm = std::move(p)](Try<T2>&& t2) mutable {
|
||||
pm.setValue(std::move(t2));
|
||||
});
|
||||
} catch (...) {
|
||||
p.setException(std::current_exception());
|
||||
}
|
||||
} else {
|
||||
p.setValue(makeTryCall(std::forward<F>(f),
|
||||
std::move(t))); // Try<Unit>
|
||||
}
|
||||
}
|
||||
});
|
||||
return newFuture;
|
||||
}
|
||||
|
||||
// continuation returns a value
|
||||
template <typename F, typename R>
|
||||
std::enable_if_t<!(R::ReturnsFuture::value),
|
||||
Future<typename R::ReturnsFuture::Inner>>
|
||||
thenImpl(F&& func) {
|
||||
logicAssert(valid(), "Future is broken");
|
||||
using T2 = typename R::ReturnsFuture::Inner;
|
||||
if (!_sharedState) {
|
||||
Future<T2> newFuture(makeTryCall(std::forward<F>(func),
|
||||
std::move(_localState.getTry())));
|
||||
newFuture.setExecutor(_localState.getExecutor());
|
||||
return newFuture;
|
||||
}
|
||||
Promise<T2> promise;
|
||||
auto newFuture = promise.getFuture();
|
||||
newFuture.setExecutor(_sharedState->getExecutor());
|
||||
_sharedState->setContinuation(
|
||||
[p = std::move(promise),
|
||||
f = std::forward<F>(func)](Try<T>&& t) mutable {
|
||||
if (!R::isTry && t.hasError()) {
|
||||
p.setException(t.getException());
|
||||
} else {
|
||||
p.setValue(makeTryCall(std::forward<F>(f),
|
||||
std::move(t))); // Try<Unit>
|
||||
}
|
||||
});
|
||||
return newFuture;
|
||||
}
|
||||
|
||||
private:
|
||||
FutureState<T>* _sharedState;
|
||||
FutureState<inner_value_type>* _sharedState;
|
||||
|
||||
// Ready-Future does not have a Promise, an inline state is faster.
|
||||
LocalState<T> _localState;
|
||||
LocalState<inner_value_type> _localState;
|
||||
|
||||
private:
|
||||
template <typename Iter>
|
||||
|
@ -321,6 +349,7 @@ template <typename T>
|
|||
Future<T> makeReadyFuture(std::exception_ptr ex) {
|
||||
return Future<T>(Try<T>(ex));
|
||||
}
|
||||
inline Future<void> makeReadyFuture() { return Future<void>(Try<Unit>()); }
|
||||
|
||||
} // namespace async_simple
|
||||
|
||||
|
|
|
@ -19,16 +19,16 @@
|
|||
#include <atomic>
|
||||
#include <iostream>
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/MoveWrapper.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <cassert>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/MoveWrapper.h"
|
||||
#include "async_simple/Try.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
@ -144,7 +144,7 @@ public:
|
|||
_attached.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
AS_INLINE void detachOne() {
|
||||
auto old = _attached.fetch_sub(1, std::memory_order_relaxed);
|
||||
auto old = _attached.fetch_sub(1, std::memory_order_acq_rel);
|
||||
assert(old >= 1u);
|
||||
if (old == 1) {
|
||||
delete this;
|
||||
|
@ -155,7 +155,7 @@ public:
|
|||
attachOne();
|
||||
}
|
||||
AS_INLINE void detachPromise() {
|
||||
auto old = _promiseRef.fetch_sub(1, std::memory_order_relaxed);
|
||||
auto old = _promiseRef.fetch_sub(1, std::memory_order_acq_rel);
|
||||
assert(old >= 1u);
|
||||
if (!hasResult() && old == 1) {
|
||||
try {
|
||||
|
@ -195,7 +195,11 @@ public:
|
|||
// ONLY_RESULT: promise.setValue called
|
||||
// ONLY_CONTINUATION: future.thenImpl called
|
||||
void setResult(Try<T>&& value) {
|
||||
#if !defined(__GNUC__) || __GNUC__ < 12
|
||||
// GCC 12 issues a spurious uninitialized-var warning.
|
||||
// See details: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109448
|
||||
logicAssert(!hasResult(), "FutureState already has a result");
|
||||
#endif
|
||||
_try_value = std::move(value);
|
||||
|
||||
auto state = _state.load(std::memory_order_acquire);
|
||||
|
|
|
@ -19,16 +19,16 @@
|
|||
|
||||
#include <atomic>
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/MoveWrapper.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/MoveWrapper.h"
|
||||
#include "async_simple/Try.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
#ifndef ASYNC_SIMPLE_MOVEWRAPPER_H
|
||||
#define ASYNC_SIMPLE_MOVEWRAPPER_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
#ifndef ASYNC_SIMPLE_PROMISE_H
|
||||
#define ASYNC_SIMPLE_PROMISE_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Future.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Future.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
@ -34,7 +34,8 @@ class Future;
|
|||
template <typename T>
|
||||
class Promise {
|
||||
public:
|
||||
Promise() : _sharedState(new FutureState<T>()), _hasFuture(false) {
|
||||
using value_type = std::conditional_t<std::is_void_v<T>, Unit, T>;
|
||||
Promise() : _sharedState(new FutureState<value_type>()), _hasFuture(false) {
|
||||
_sharedState->attachPromise();
|
||||
}
|
||||
~Promise() {
|
||||
|
@ -95,19 +96,24 @@ public:
|
|||
public:
|
||||
void setException(std::exception_ptr error) {
|
||||
logicAssert(valid(), "Promise is broken");
|
||||
_sharedState->setResult(Try<T>(error));
|
||||
_sharedState->setResult(Try<value_type>(error));
|
||||
}
|
||||
void setValue(T&& v) {
|
||||
void setValue(value_type&& v) requires(!std::is_void_v<T>) {
|
||||
logicAssert(valid(), "Promise is broken");
|
||||
_sharedState->setResult(Try<T>(std::forward<T>(v)));
|
||||
_sharedState->setResult(Try<value_type>(std::forward<T>(v)));
|
||||
}
|
||||
void setValue(Try<T>&& t) {
|
||||
void setValue(Try<value_type>&& t) {
|
||||
logicAssert(valid(), "Promise is broken");
|
||||
_sharedState->setResult(std::move(t));
|
||||
}
|
||||
|
||||
void setValue() requires(std::is_void_v<T>) {
|
||||
logicAssert(valid(), "Promise is broken");
|
||||
_sharedState->setResult(Try<value_type>(Unit()));
|
||||
}
|
||||
|
||||
private:
|
||||
FutureState<T>* _sharedState = nullptr;
|
||||
FutureState<value_type>* _sharedState = nullptr;
|
||||
bool _hasFuture = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
#ifndef ASYNC_SIMPLE_TRAITS_H
|
||||
#define ASYNC_SIMPLE_TRAITS_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <async_simple/Unit.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Try.h"
|
||||
#include "async_simple/Unit.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
@ -31,11 +31,6 @@ struct IsFuture : std::false_type {
|
|||
using Inner = T;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IsFuture<void> : std::false_type {
|
||||
using Inner = Unit;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsFuture<Future<T>> : std::true_type {
|
||||
using Inner = T;
|
||||
|
@ -55,6 +50,13 @@ struct ValueCallableResult {
|
|||
static constexpr bool isTry = false;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
struct ValueCallableResult<void, F> {
|
||||
using Result = std::invoke_result_t<F>;
|
||||
using ReturnsFuture = IsFuture<Result>;
|
||||
static constexpr bool isTry = false;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct remove_cvref {
|
||||
|
|
|
@ -16,15 +16,23 @@
|
|||
#ifndef ASYNC_SIMPLE_TRY_H
|
||||
#define ASYNC_SIMPLE_TRY_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Unit.h>
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Unit.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
// Forward declaration
|
||||
template <typename T>
|
||||
class Try;
|
||||
|
||||
template <>
|
||||
class Try<void>;
|
||||
// Try<T> contains either an instance of T, an exception, or nothing.
|
||||
// Try<T>::value() will return the instance.
|
||||
// If exception or nothing inside, Try<T>::value() would throw an exception.
|
||||
|
@ -36,133 +44,101 @@ namespace async_simple {
|
|||
// 4. moved from another Try<T> instance.
|
||||
template <typename T>
|
||||
class Try {
|
||||
private:
|
||||
enum class Contains {
|
||||
VALUE,
|
||||
EXCEPTION,
|
||||
NOTHING,
|
||||
};
|
||||
|
||||
public:
|
||||
Try() : _contains(Contains::NOTHING) {}
|
||||
~Try() { destroy(); }
|
||||
Try() = default;
|
||||
~Try() = default;
|
||||
|
||||
Try(Try<T>&& other) : _contains(other._contains) {
|
||||
if (_contains == Contains::VALUE) {
|
||||
new (&_value) T(std::move(other._value));
|
||||
} else if (_contains == Contains::EXCEPTION) {
|
||||
new (&_error) std::exception_ptr(other._error);
|
||||
}
|
||||
}
|
||||
Try(Try<T>&& other) = default;
|
||||
template <typename T2 = T>
|
||||
Try(std::enable_if_t<std::is_same<Unit, T2>::value, const Try<void>&>
|
||||
other) {
|
||||
if (other.hasError()) {
|
||||
_contains = Contains::EXCEPTION;
|
||||
new (&_error) std::exception_ptr(other._error);
|
||||
_value.template emplace<std::exception_ptr>(other._error);
|
||||
} else {
|
||||
_contains = Contains::VALUE;
|
||||
new (&_value) T();
|
||||
_value.template emplace<T>();
|
||||
}
|
||||
}
|
||||
Try& operator=(Try<T>&& other) {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
destroy();
|
||||
|
||||
_contains = other._contains;
|
||||
if (_contains == Contains::VALUE) {
|
||||
new (&_value) T(std::move(other._value));
|
||||
} else if (_contains == Contains::EXCEPTION) {
|
||||
new (&_error) std::exception_ptr(other._error);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Try& operator=(Try<T>&& other) = default;
|
||||
Try& operator=(std::exception_ptr error) {
|
||||
if (_contains == Contains::EXCEPTION && error == this->_error) {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value) &&
|
||||
std::get<std::exception_ptr>(_value) == error) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
destroy();
|
||||
|
||||
_contains = Contains::EXCEPTION;
|
||||
new (&_error) std::exception_ptr(error);
|
||||
_value.template emplace<std::exception_ptr>(error);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Try(const T& val) : _contains(Contains::VALUE), _value(val) {}
|
||||
Try(T&& val) : _contains(Contains::VALUE), _value(std::move(val)) {}
|
||||
Try(std::exception_ptr error)
|
||||
: _contains(Contains::EXCEPTION), _error(error) {}
|
||||
template <class... U>
|
||||
Try(U&&... value)
|
||||
requires std::is_constructible_v<T, U...>
|
||||
: _value(std::in_place_type<T>, std::forward<U>(value)...) {}
|
||||
|
||||
Try(std::exception_ptr error) : _value(error) {}
|
||||
|
||||
private:
|
||||
Try(const Try&) = delete;
|
||||
Try& operator=(const Try&) = delete;
|
||||
|
||||
public:
|
||||
bool available() const { return _contains != Contains::NOTHING; }
|
||||
bool hasError() const { return _contains == Contains::EXCEPTION; }
|
||||
constexpr bool available() const noexcept {
|
||||
return !std::holds_alternative<std::monostate>(_value);
|
||||
}
|
||||
constexpr bool hasError() const noexcept {
|
||||
return std::holds_alternative<std::exception_ptr>(_value);
|
||||
}
|
||||
const T& value() const& {
|
||||
checkHasTry();
|
||||
return _value;
|
||||
return std::get<T>(_value);
|
||||
}
|
||||
T& value() & {
|
||||
checkHasTry();
|
||||
return _value;
|
||||
return std::get<T>(_value);
|
||||
}
|
||||
T&& value() && {
|
||||
checkHasTry();
|
||||
return std::move(_value);
|
||||
return std::move(std::get<T>(_value));
|
||||
}
|
||||
const T&& value() const&& {
|
||||
checkHasTry();
|
||||
return std::move(_value);
|
||||
return std::move(std::get<T>(_value));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T& emplace(Args&&... args) {
|
||||
return _value.template emplace<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void setException(std::exception_ptr error) {
|
||||
if (_contains == Contains::EXCEPTION && _error == error) {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value) &&
|
||||
std::get<std::exception_ptr>(_value) == error) {
|
||||
return;
|
||||
}
|
||||
destroy();
|
||||
_contains = Contains::EXCEPTION;
|
||||
new (&_error) std::exception_ptr(error);
|
||||
_value.template emplace<std::exception_ptr>(error);
|
||||
}
|
||||
std::exception_ptr getException() {
|
||||
logicAssert(_contains == Contains::EXCEPTION,
|
||||
"Try object do not has an error");
|
||||
return _error;
|
||||
std::exception_ptr getException() const {
|
||||
logicAssert(std::holds_alternative<std::exception_ptr>(_value),
|
||||
"Try object do not has on error");
|
||||
return std::get<std::exception_ptr>(_value);
|
||||
}
|
||||
|
||||
operator Try<void>() const;
|
||||
|
||||
private:
|
||||
AS_INLINE void checkHasTry() const {
|
||||
if (_contains == Contains::VALUE)
|
||||
if (std::holds_alternative<T>(_value))
|
||||
AS_LIKELY { return; }
|
||||
else if (_contains == Contains::EXCEPTION) {
|
||||
std::rethrow_exception(_error);
|
||||
} else if (_contains == Contains::NOTHING) {
|
||||
else if (std::holds_alternative<std::exception_ptr>(_value)) {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(_value));
|
||||
} else if (std::holds_alternative<std::monostate>(_value)) {
|
||||
throw std::logic_error("Try object is empty");
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (_contains == Contains::VALUE) {
|
||||
_value.~T();
|
||||
} else if (_contains == Contains::EXCEPTION) {
|
||||
_error.~exception_ptr();
|
||||
}
|
||||
_contains = Contains::NOTHING;
|
||||
}
|
||||
|
||||
private:
|
||||
Contains _contains = Contains::NOTHING;
|
||||
union {
|
||||
T _value;
|
||||
std::exception_ptr _error;
|
||||
};
|
||||
std::variant<std::monostate, T, std::exception_ptr> _value;
|
||||
|
||||
private:
|
||||
friend Try<Unit>;
|
||||
|
@ -207,29 +183,30 @@ private:
|
|||
friend Try<Unit>;
|
||||
};
|
||||
|
||||
// T is Non void
|
||||
template <typename F, typename... Args>
|
||||
std::enable_if_t<!(std::is_same<std::invoke_result_t<F, Args...>, void>::value),
|
||||
Try<std::invoke_result_t<F, Args...>>>
|
||||
makeTryCall(F&& f, Args&&... args) {
|
||||
using T = std::invoke_result_t<F, Args...>;
|
||||
try {
|
||||
return Try<T>(std::forward<F>(f)(std::forward<Args>(args)...));
|
||||
} catch (...) {
|
||||
return Try<T>(std::current_exception());
|
||||
template <class T>
|
||||
Try<T>::operator Try<void>() const {
|
||||
if (hasError()) {
|
||||
return Try<void>(getException());
|
||||
}
|
||||
return Try<void>();
|
||||
}
|
||||
|
||||
// T is void
|
||||
template <class T>
|
||||
Try(T) -> Try<T>;
|
||||
|
||||
template <typename F, typename... Args>
|
||||
std::enable_if_t<std::is_same<std::invoke_result_t<F, Args...>, void>::value,
|
||||
Try<void>>
|
||||
makeTryCall(F&& f, Args&&... args) {
|
||||
auto makeTryCall(F&& f, Args&&... args) {
|
||||
using T = std::invoke_result_t<F, Args...>;
|
||||
try {
|
||||
std::forward<F>(f)(std::forward<Args>(args)...);
|
||||
return Try<void>();
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
return Try<void>();
|
||||
} else {
|
||||
return Try<T>(
|
||||
std::invoke(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
}
|
||||
} catch (...) {
|
||||
return Try<void>(std::current_exception());
|
||||
return Try<T>(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
#ifndef ASYNC_SIMPLE_UNIT_H
|
||||
#define ASYNC_SIMPLE_UNIT_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Try.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -16,11 +16,7 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_COLLECT_H
|
||||
#define ASYNC_SIMPLE_CORO_COLLECT_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <async_simple/coro/CountEvent.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
#include <array>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -28,6 +24,11 @@
|
|||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Try.h"
|
||||
#include "async_simple/coro/CountEvent.h"
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
@ -46,7 +47,8 @@ namespace detail {
|
|||
template <typename T>
|
||||
struct CollectAnyResult {
|
||||
CollectAnyResult() : _idx(static_cast<size_t>(-1)), _value() {}
|
||||
CollectAnyResult(size_t idx, T&& value)
|
||||
CollectAnyResult(size_t idx, std::add_rvalue_reference_t<T> value) requires(
|
||||
!std::is_void_v<T>)
|
||||
: _idx(idx), _value(std::move(value)) {}
|
||||
|
||||
CollectAnyResult(const CollectAnyResult&) = delete;
|
||||
|
@ -58,13 +60,29 @@ struct CollectAnyResult {
|
|||
|
||||
size_t _idx;
|
||||
Try<T> _value;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct CollectAnyResult<void> {
|
||||
CollectAnyResult() : _idx(static_cast<size_t>(-1)) {}
|
||||
size_t _idx;
|
||||
Try<void> _value;
|
||||
size_t index() const { return _idx; }
|
||||
|
||||
bool hasError() const { return _value.hasError(); }
|
||||
// Require hasError() == true. Otherwise it is UB to call
|
||||
// this method.
|
||||
std::exception_ptr getException() const {
|
||||
return _value.getException();
|
||||
}
|
||||
|
||||
// Require hasError() == false. Otherwise it is UB to call
|
||||
// value() method.
|
||||
#if __cpp_explicit_this_parameter >= 202110L
|
||||
template <class Self>
|
||||
auto&& value(this Self&& self) {
|
||||
return std::forward<Self>(self)._value.value();
|
||||
}
|
||||
#else
|
||||
const T& value() const& { return _value.value(); }
|
||||
T& value() & { return _value.value(); }
|
||||
T&& value() && { return std::move(_value).value(); }
|
||||
const T&& value() const&& { return std::move(_value).value(); }
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename LazyType, typename InAlloc>
|
||||
|
@ -137,6 +155,10 @@ struct CollectAnyVariadicAwaiter {
|
|||
: _input(std::make_unique<InputType>(std::move(inputs)...)),
|
||||
_result(nullptr) {}
|
||||
|
||||
CollectAnyVariadicAwaiter(InputType&& inputs)
|
||||
: _input(std::make_unique<InputType>(std::move(inputs))),
|
||||
_result(nullptr) {}
|
||||
|
||||
CollectAnyVariadicAwaiter(const CollectAnyVariadicAwaiter&) = delete;
|
||||
CollectAnyVariadicAwaiter& operator=(const CollectAnyVariadicAwaiter&) =
|
||||
delete;
|
||||
|
@ -144,8 +166,7 @@ struct CollectAnyVariadicAwaiter {
|
|||
: _input(std::move(other._input)), _result(std::move(other._result)) {}
|
||||
|
||||
bool await_ready() const noexcept {
|
||||
return std::tuple_size<InputType>() == 0 ||
|
||||
(_result && _result->has_value());
|
||||
return _result && _result->has_value();
|
||||
}
|
||||
|
||||
template <size_t... index>
|
||||
|
@ -221,19 +242,29 @@ struct SimpleCollectAnyAwaitable {
|
|||
}
|
||||
};
|
||||
|
||||
template <typename LazyType, typename IAlloc, typename OAlloc,
|
||||
bool Para = false>
|
||||
struct CollectAllAwaiter {
|
||||
using ValueType = typename LazyType::ValueType;
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
struct SimpleCollectAnyVariadicAwaiter {
|
||||
using InputType = std::tuple<LazyType<Ts>...>;
|
||||
|
||||
CollectAllAwaiter(std::vector<LazyType, IAlloc>&& input, OAlloc outAlloc)
|
||||
InputType _inputs;
|
||||
|
||||
SimpleCollectAnyVariadicAwaiter(LazyType<Ts>&&... inputs)
|
||||
: _inputs(std::move(inputs)...) {}
|
||||
|
||||
auto coAwait(Executor* ex) {
|
||||
return CollectAnyVariadicAwaiter(std::move(_inputs));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container, typename OAlloc, bool Para = false>
|
||||
struct CollectAllAwaiter {
|
||||
using ValueType = typename Container::value_type::ValueType;
|
||||
|
||||
CollectAllAwaiter(Container&& input, OAlloc outAlloc)
|
||||
: _input(std::move(input)), _output(outAlloc), _event(_input.size()) {
|
||||
_output.resize(_input.size());
|
||||
}
|
||||
CollectAllAwaiter(CollectAllAwaiter&& other)
|
||||
: _input(std::move(other._input)),
|
||||
_output(std::move(other._output)),
|
||||
_event(std::move(other._event)) {}
|
||||
CollectAllAwaiter(CollectAllAwaiter&& other) = default;
|
||||
|
||||
CollectAllAwaiter(const CollectAllAwaiter&) = delete;
|
||||
CollectAllAwaiter& operator=(const CollectAllAwaiter&) = delete;
|
||||
|
@ -276,26 +307,22 @@ struct CollectAllAwaiter {
|
|||
}
|
||||
inline auto await_resume() { return std::move(_output); }
|
||||
|
||||
std::vector<LazyType, IAlloc> _input;
|
||||
Container _input;
|
||||
std::vector<Try<ValueType>, OAlloc> _output;
|
||||
detail::CountEvent _event;
|
||||
}; // CollectAllAwaiter
|
||||
|
||||
template <typename T, typename IAlloc, typename OAlloc, bool Para = false>
|
||||
template <class Container, typename OAlloc, bool Para = false>
|
||||
struct SimpleCollectAllAwaitable {
|
||||
using ValueType = T;
|
||||
using LazyType = Lazy<T>;
|
||||
using VectorType = std::vector<LazyType, IAlloc>;
|
||||
|
||||
VectorType _input;
|
||||
Container _input;
|
||||
OAlloc _out_alloc;
|
||||
|
||||
SimpleCollectAllAwaitable(VectorType&& input, OAlloc out_alloc)
|
||||
SimpleCollectAllAwaitable(Container&& input, OAlloc out_alloc)
|
||||
: _input(std::move(input)), _out_alloc(out_alloc) {}
|
||||
|
||||
auto coAwait(Executor* ex) {
|
||||
return CollectAllAwaiter<LazyType, IAlloc, OAlloc, Para>(
|
||||
std::move(_input), _out_alloc);
|
||||
return CollectAllAwaiter<Container, OAlloc, Para>(std::move(_input),
|
||||
_out_alloc);
|
||||
}
|
||||
}; // SimpleCollectAllAwaitable
|
||||
|
||||
|
@ -303,32 +330,38 @@ struct SimpleCollectAllAwaitable {
|
|||
|
||||
namespace detail {
|
||||
|
||||
template <bool Para, typename T, template <typename> typename LazyType,
|
||||
typename IAlloc = std::allocator<LazyType<T>>,
|
||||
template <typename T>
|
||||
struct is_lazy : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_lazy<Lazy<T>> : std::true_type {};
|
||||
|
||||
template <bool Para, class Container,
|
||||
typename T = typename Container::value_type::ValueType,
|
||||
typename OAlloc = std::allocator<Try<T>>>
|
||||
inline auto collectAllImpl(std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
inline auto collectAllImpl(Container input, OAlloc out_alloc = OAlloc()) {
|
||||
using LazyType = typename Container::value_type;
|
||||
using AT = std::conditional_t<
|
||||
std::is_same_v<LazyType<T>, Lazy<T>>,
|
||||
detail::SimpleCollectAllAwaitable<T, IAlloc, OAlloc, Para>,
|
||||
detail::CollectAllAwaiter<LazyType<T>, IAlloc, OAlloc, Para>>;
|
||||
co_return co_await AT(std::move(input), out_alloc);
|
||||
is_lazy<LazyType>::value,
|
||||
detail::SimpleCollectAllAwaitable<Container, OAlloc, Para>,
|
||||
detail::CollectAllAwaiter<Container, OAlloc, Para>>;
|
||||
return AT(std::move(input), out_alloc);
|
||||
}
|
||||
|
||||
template <bool Para, typename T, template <typename> typename LazyType,
|
||||
typename IAlloc = std::allocator<LazyType<T>>,
|
||||
template <bool Para, class Container,
|
||||
typename T = typename Container::value_type::ValueType,
|
||||
typename OAlloc = std::allocator<Try<T>>>
|
||||
inline auto collectAllWindowedImpl(size_t maxConcurrency,
|
||||
bool yield /*yield between two batchs*/,
|
||||
std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
Container input, OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
using LazyType = typename Container::value_type;
|
||||
using AT = std::conditional_t<
|
||||
std::is_same_v<LazyType<T>, Lazy<T>>,
|
||||
detail::SimpleCollectAllAwaitable<T, IAlloc, OAlloc, Para>,
|
||||
detail::CollectAllAwaiter<LazyType<T>, IAlloc, OAlloc, Para>>;
|
||||
is_lazy<LazyType>::value,
|
||||
detail::SimpleCollectAllAwaitable<Container, OAlloc, Para>,
|
||||
detail::CollectAllAwaiter<Container, OAlloc, Para>>;
|
||||
std::vector<Try<T>, OAlloc> output(out_alloc);
|
||||
output.reserve(input.size());
|
||||
size_t input_size = input.size();
|
||||
// maxConcurrency == 0;
|
||||
// input_size <= maxConcurrency size;
|
||||
|
@ -339,12 +372,11 @@ inline auto collectAllWindowedImpl(size_t maxConcurrency,
|
|||
size_t start = 0;
|
||||
while (start < input_size) {
|
||||
size_t end = std::min(input_size, start + maxConcurrency);
|
||||
std::vector<LazyType<T>, IAlloc> tmp_group(input.get_allocator());
|
||||
for (; start < end; ++start) {
|
||||
tmp_group.push_back(std::move(input[start]));
|
||||
}
|
||||
auto tmp_output = co_await AT(std::move(tmp_group), out_alloc);
|
||||
for (auto& t : tmp_output) {
|
||||
std::vector<LazyType> tmp_group(
|
||||
std::make_move_iterator(input.begin() + start),
|
||||
std::make_move_iterator(input.begin() + end));
|
||||
start = end;
|
||||
for (auto& t : co_await AT(std::move(tmp_group), out_alloc)) {
|
||||
output.push_back(std::move(t));
|
||||
}
|
||||
if (yield) {
|
||||
|
@ -356,68 +388,137 @@ inline auto collectAllWindowedImpl(size_t maxConcurrency,
|
|||
|
||||
// variadic collectAll
|
||||
|
||||
template <template <typename> typename LazyType, typename Ts>
|
||||
Lazy<void> makeWraperTask(LazyType<Ts>&& awaitable, Try<Ts>& result) {
|
||||
try {
|
||||
if constexpr (std::is_void_v<Ts>) {
|
||||
co_await awaitable;
|
||||
} else {
|
||||
result = co_await awaitable;
|
||||
template <bool Para, template <typename> typename LazyType, typename... Ts>
|
||||
struct CollectAllVariadicAwaiter {
|
||||
using ResultType = std::tuple<Try<Ts>...>;
|
||||
using InputType = std::tuple<LazyType<Ts>...>;
|
||||
|
||||
CollectAllVariadicAwaiter(LazyType<Ts>&&... inputs)
|
||||
: _inputs(std::move(inputs)...), _event(sizeof...(Ts)) {}
|
||||
CollectAllVariadicAwaiter(InputType&& inputs)
|
||||
: _inputs(std::move(inputs)), _event(sizeof...(Ts)) {}
|
||||
|
||||
CollectAllVariadicAwaiter(const CollectAllVariadicAwaiter&) = delete;
|
||||
CollectAllVariadicAwaiter& operator=(const CollectAllVariadicAwaiter&) =
|
||||
delete;
|
||||
CollectAllVariadicAwaiter(CollectAllVariadicAwaiter&&) = default;
|
||||
|
||||
bool await_ready() const noexcept { return false; }
|
||||
|
||||
template <size_t... index>
|
||||
void await_suspend_impl(std::index_sequence<index...>,
|
||||
std::coroutine_handle<> continuation) {
|
||||
auto promise_type =
|
||||
std::coroutine_handle<LazyPromiseBase>::from_address(
|
||||
continuation.address())
|
||||
.promise();
|
||||
auto executor = promise_type._executor;
|
||||
|
||||
_event.setAwaitingCoro(continuation);
|
||||
|
||||
// fold expression
|
||||
(
|
||||
[executor, this](auto& lazy, auto& result) {
|
||||
auto& exec = lazy._coro.promise()._executor;
|
||||
if (exec == nullptr) {
|
||||
exec = executor;
|
||||
}
|
||||
auto func = [&]() {
|
||||
lazy.start([&](auto&& res) {
|
||||
result = std::move(res);
|
||||
if (auto awaitingCoro = _event.down(); awaitingCoro) {
|
||||
awaitingCoro.resume();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if constexpr (Para == true && sizeof...(Ts) > 1) {
|
||||
if (exec != nullptr)
|
||||
AS_LIKELY { exec->schedule(std::move(func)); }
|
||||
else
|
||||
AS_UNLIKELY { func(); }
|
||||
} else {
|
||||
func();
|
||||
}
|
||||
}(std::get<index>(_inputs), std::get<index>(_results)),
|
||||
...);
|
||||
|
||||
if (auto awaitingCoro = _event.down(); awaitingCoro) {
|
||||
awaitingCoro.resume();
|
||||
}
|
||||
} catch (...) {
|
||||
result.setException(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
template <bool Para, template <typename> typename LazyType, typename... Ts,
|
||||
size_t... Indices>
|
||||
inline auto collectAllVariadicImpl(std::index_sequence<Indices...>,
|
||||
LazyType<Ts>&&... awaitables)
|
||||
-> Lazy<std::tuple<Try<Ts>...>> {
|
||||
void await_suspend(std::coroutine_handle<> continuation) {
|
||||
await_suspend_impl(std::make_index_sequence<sizeof...(Ts)>{},
|
||||
std::move(continuation));
|
||||
}
|
||||
|
||||
auto await_resume() { return std::move(_results); }
|
||||
|
||||
InputType _inputs;
|
||||
ResultType _results;
|
||||
detail::CountEvent _event;
|
||||
};
|
||||
|
||||
template <bool Para, template <typename> typename LazyType, typename... Ts>
|
||||
struct SimpleCollectAllVariadicAwaiter {
|
||||
using InputType = std::tuple<LazyType<Ts>...>;
|
||||
|
||||
SimpleCollectAllVariadicAwaiter(LazyType<Ts>&&... inputs)
|
||||
: _input(std::move(inputs)...) {}
|
||||
|
||||
auto coAwait(Executor* ex) {
|
||||
return CollectAllVariadicAwaiter<Para, LazyType, Ts...>(
|
||||
std::move(_input));
|
||||
}
|
||||
|
||||
InputType _input;
|
||||
};
|
||||
|
||||
template <bool Para, template <typename> typename LazyType, typename... Ts>
|
||||
inline auto collectAllVariadicImpl(LazyType<Ts>&&... awaitables) {
|
||||
static_assert(sizeof...(Ts) > 0);
|
||||
|
||||
std::tuple<Try<Ts>...> results;
|
||||
std::vector<Lazy<void>> wraper_tasks;
|
||||
|
||||
// make wraper task
|
||||
(void)std::initializer_list<bool>{
|
||||
(wraper_tasks.push_back(std::move(makeWraperTask(
|
||||
std::move(awaitables), std::get<Indices>(results)))),
|
||||
true)...,
|
||||
};
|
||||
|
||||
co_await collectAllImpl<Para>(std::move(wraper_tasks));
|
||||
co_return std::move(results);
|
||||
using AT = std::conditional_t<
|
||||
is_lazy<LazyType<void>>::value && !Para,
|
||||
SimpleCollectAllVariadicAwaiter<Para, LazyType, Ts...>,
|
||||
CollectAllVariadicAwaiter<Para, LazyType, Ts...>>;
|
||||
return AT(std::move(awaitables)...);
|
||||
}
|
||||
|
||||
// collectAny
|
||||
|
||||
template <typename T, template <typename> typename LazyType,
|
||||
typename IAlloc = std::allocator<LazyType<T>>>
|
||||
inline auto collectAnyImpl(std::vector<LazyType<T>, IAlloc>&& input)
|
||||
-> Lazy<detail::CollectAnyResult<T>> {
|
||||
inline auto collectAnyImpl(std::vector<LazyType<T>, IAlloc> input) {
|
||||
using AT =
|
||||
std::conditional_t<std::is_same_v<LazyType<T>, Lazy<T>>,
|
||||
detail::SimpleCollectAnyAwaitable<T, IAlloc>,
|
||||
detail::CollectAnyAwaiter<LazyType<T>, IAlloc>>;
|
||||
co_return co_await AT(std::move(input));
|
||||
return AT(std::move(input));
|
||||
}
|
||||
|
||||
// collectAnyVariadic
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
inline auto CollectAnyVariadicImpl(LazyType<Ts>&&... inputs) {
|
||||
using AT =
|
||||
std::conditional_t<is_lazy<LazyType<void>>::value,
|
||||
SimpleCollectAnyVariadicAwaiter<LazyType, Ts...>,
|
||||
CollectAnyVariadicAwaiter<LazyType, Ts...>>;
|
||||
return AT(std::move(inputs)...);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, template <typename> typename LazyType,
|
||||
typename IAlloc = std::allocator<LazyType<T>>>
|
||||
inline auto collectAny(std::vector<LazyType<T>, IAlloc>&& input)
|
||||
-> Lazy<detail::CollectAnyResult<T>> {
|
||||
co_return co_await detail::collectAnyImpl(std::move(input));
|
||||
inline auto collectAny(std::vector<LazyType<T>, IAlloc>&& input) {
|
||||
return detail::collectAnyImpl(std::move(input));
|
||||
}
|
||||
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
inline auto collectAny(LazyType<Ts>... awaitables)
|
||||
-> Lazy<std::variant<Try<Ts>...>> {
|
||||
inline auto collectAny(LazyType<Ts>... awaitables) {
|
||||
static_assert(sizeof...(Ts), "collectAny need at least one param!");
|
||||
co_return co_await detail::CollectAnyVariadicAwaiter(
|
||||
std::move(awaitables)...);
|
||||
return detail::CollectAnyVariadicImpl(std::move(awaitables)...);
|
||||
}
|
||||
|
||||
// The collectAll() function can be used to co_await on a vector of LazyType
|
||||
|
@ -427,10 +528,8 @@ template <typename T, template <typename> typename LazyType,
|
|||
typename IAlloc = std::allocator<LazyType<T>>,
|
||||
typename OAlloc = std::allocator<Try<T>>>
|
||||
inline auto collectAll(std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
co_return co_await detail::collectAllImpl<false>(std::move(input),
|
||||
out_alloc);
|
||||
OAlloc out_alloc = OAlloc()) {
|
||||
return detail::collectAllImpl<false>(std::move(input), out_alloc);
|
||||
}
|
||||
|
||||
// Like the collectAll() function above, The collectAllPara() function can be
|
||||
|
@ -440,10 +539,8 @@ template <typename T, template <typename> typename LazyType,
|
|||
typename IAlloc = std::allocator<LazyType<T>>,
|
||||
typename OAlloc = std::allocator<Try<T>>>
|
||||
inline auto collectAllPara(std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
co_return co_await detail::collectAllImpl<true>(std::move(input),
|
||||
out_alloc);
|
||||
OAlloc out_alloc = OAlloc()) {
|
||||
return detail::collectAllImpl<true>(std::move(input), out_alloc);
|
||||
}
|
||||
|
||||
// This collectAll function can be used to co_await on some different kinds of
|
||||
|
@ -453,27 +550,18 @@ template <template <typename> typename LazyType, typename... Ts>
|
|||
// The temporary object's life-time which binding to reference(inputs) won't
|
||||
// be extended to next time of coroutine resume. Just Copy inputs to avoid
|
||||
// crash.
|
||||
inline auto collectAll(LazyType<Ts>... inputs) -> Lazy<std::tuple<Try<Ts>...>> {
|
||||
if constexpr (0 == sizeof...(Ts)) {
|
||||
co_return std::tuple<>{};
|
||||
} else {
|
||||
co_return co_await detail::collectAllVariadicImpl<false>(
|
||||
std::make_index_sequence<sizeof...(Ts)>{}, std::move(inputs)...);
|
||||
}
|
||||
inline auto collectAll(LazyType<Ts>... inputs) {
|
||||
static_assert(sizeof...(Ts), "collectAll need at least one param!");
|
||||
return detail::collectAllVariadicImpl<false>(std::move(inputs)...);
|
||||
}
|
||||
|
||||
// Like the collectAll() function above, This collectAllPara() function can be
|
||||
// used to concurrently co_await on some different kinds of LazyType tasks in
|
||||
// executor,and producing a tuple of Try values containing each of the results.
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
inline auto collectAllPara(LazyType<Ts>... inputs)
|
||||
-> Lazy<std::tuple<Try<Ts>...>> {
|
||||
if constexpr (0 == sizeof...(Ts)) {
|
||||
co_return std::tuple<>{};
|
||||
} else {
|
||||
co_return co_await detail::collectAllVariadicImpl<true>(
|
||||
std::make_index_sequence<sizeof...(Ts)>{}, std::move(inputs)...);
|
||||
}
|
||||
inline auto collectAllPara(LazyType<Ts>... inputs) {
|
||||
static_assert(sizeof...(Ts), "collectAllPara need at least one param!");
|
||||
return detail::collectAllVariadicImpl<true>(std::move(inputs)...);
|
||||
}
|
||||
|
||||
// Await each of the input LazyType tasks in the vector, allowing at most
|
||||
|
@ -486,10 +574,9 @@ template <typename T, template <typename> typename LazyType,
|
|||
inline auto collectAllWindowed(size_t maxConcurrency,
|
||||
bool yield /*yield between two batchs*/,
|
||||
std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
co_return co_await detail::collectAllWindowedImpl<true>(
|
||||
maxConcurrency, yield, std::move(input), out_alloc);
|
||||
OAlloc out_alloc = OAlloc()) {
|
||||
return detail::collectAllWindowedImpl<true>(maxConcurrency, yield,
|
||||
std::move(input), out_alloc);
|
||||
}
|
||||
|
||||
// Await each of the input LazyType tasks in the vector, allowing at most
|
||||
|
@ -503,10 +590,9 @@ template <typename T, template <typename> typename LazyType,
|
|||
inline auto collectAllWindowedPara(size_t maxConcurrency,
|
||||
bool yield /*yield between two batchs*/,
|
||||
std::vector<LazyType<T>, IAlloc>&& input,
|
||||
OAlloc out_alloc = OAlloc())
|
||||
-> Lazy<std::vector<Try<T>, OAlloc>> {
|
||||
co_return co_await detail::collectAllWindowedImpl<false>(
|
||||
maxConcurrency, yield, std::move(input), out_alloc);
|
||||
OAlloc out_alloc = OAlloc()) {
|
||||
return detail::collectAllWindowedImpl<false>(maxConcurrency, yield,
|
||||
std::move(input), out_alloc);
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_CONDITION_VARIABLE_H
|
||||
#define ASYNC_SIMPLE_CORO_CONDITION_VARIABLE_H
|
||||
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
@ -33,7 +33,9 @@ public:
|
|||
ConditionVariable(const ConditionVariable&) = delete;
|
||||
ConditionVariable& operator=(const ConditionVariable&) = delete;
|
||||
|
||||
void notify() noexcept;
|
||||
void notify() noexcept { notifyAll(); }
|
||||
void notifyOne() noexcept;
|
||||
void notifyAll() noexcept;
|
||||
|
||||
template <class Pred>
|
||||
Lazy<> wait(Lock& lock, Pred&& pred) noexcept;
|
||||
|
@ -87,7 +89,7 @@ inline Lazy<> ConditionVariable<Lock>::wait(Lock& lock, Pred&& pred) noexcept {
|
|||
}
|
||||
|
||||
template <class Lock>
|
||||
inline void ConditionVariable<Lock>::notify() noexcept {
|
||||
inline void ConditionVariable<Lock>::notifyAll() noexcept {
|
||||
auto awaitings = _awaiters.load(std::memory_order_relaxed);
|
||||
while (!_awaiters.compare_exchange_weak(awaitings, nullptr,
|
||||
std::memory_order_release,
|
||||
|
@ -96,6 +98,20 @@ inline void ConditionVariable<Lock>::notify() noexcept {
|
|||
resumeWaiters(awaitings);
|
||||
}
|
||||
|
||||
template <class Lock>
|
||||
inline void ConditionVariable<Lock>::notifyOne() noexcept {
|
||||
auto awaitings = _awaiters.load(std::memory_order_relaxed);
|
||||
if (!awaitings) {
|
||||
return;
|
||||
}
|
||||
while (!_awaiters.compare_exchange_weak(awaitings, awaitings->_next,
|
||||
std::memory_order_release,
|
||||
std::memory_order_relaxed))
|
||||
;
|
||||
awaitings->_next = nullptr;
|
||||
resumeWaiters(awaitings);
|
||||
}
|
||||
|
||||
template <class Lock>
|
||||
inline void ConditionVariable<Lock>::resumeWaiters(
|
||||
ConditionVariableAwaiter<Lock>* awaiters) {
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_EVENT_H
|
||||
#define ASYNC_SIMPLE_CORO_EVENT_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_DETACHED_COROUTINE_H
|
||||
#define ASYNC_SIMPLE_CORO_DETACHED_COROUTINE_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
#include <stdio.h>
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -16,35 +16,33 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_FUTURE_AWAITER_H
|
||||
#define ASYNC_SIMPLE_CORO_FUTURE_AWAITER_H
|
||||
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
#include "async_simple/Future.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
||||
template <typename T>
|
||||
class FutureAwaiter {
|
||||
public:
|
||||
explicit FutureAwaiter(Future<T>&& future) : _future(std::move(future)) {}
|
||||
FutureAwaiter(FutureAwaiter&& rhs) : _future(std::move(rhs._future)) {}
|
||||
FutureAwaiter(FutureAwaiter&) = delete;
|
||||
auto operator co_await(Future<T>&& future) {
|
||||
struct FutureAwaiter {
|
||||
Future<T> future_;
|
||||
|
||||
bool await_ready() { return _future.hasResult(); }
|
||||
void await_suspend(CoroHandle<> continuation) {
|
||||
_future.setContinuation(
|
||||
[continuation](Try<T>&& t) mutable { continuation.resume(); });
|
||||
}
|
||||
T await_resume() { return std::move(_future.value()); }
|
||||
bool await_ready() { return future_.hasResult(); }
|
||||
void await_suspend(coro::CoroHandle<> continuation) {
|
||||
future_.setContinuation(
|
||||
[continuation](Try<T>&& t) mutable { continuation.resume(); });
|
||||
}
|
||||
auto await_resume() { return std::move(future_.value()); }
|
||||
};
|
||||
|
||||
private:
|
||||
Future<T> _future;
|
||||
};
|
||||
} // namespace coro
|
||||
|
||||
template <typename T>
|
||||
auto operator co_await(T&& future) requires IsFuture<std::decay_t<T>>::value {
|
||||
return coro::FutureAwaiter(std::move(future));
|
||||
return FutureAwaiter{std::move(future)};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[deprecated("Require an rvalue future.")]]
|
||||
auto operator co_await(T&& future) requires IsFuture<std::decay_t<T>>::value {
|
||||
return std::move(operator co_await(std::move(future)));
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,621 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ASYNC_SIMPLE_CORO_GENERATOR_H
|
||||
#define ASYNC_SIMPLE_CORO_GENERATOR_H
|
||||
|
||||
#if __has_include(<generator>)
|
||||
|
||||
#include <generator>
|
||||
|
||||
namespace async_simple::coro {
|
||||
|
||||
template <class Ref, class V = void, class Allocator = void>
|
||||
using Generator = std::generator<Ref, V, AlloAllocator>;
|
||||
|
||||
} // namespace async_simple::coro
|
||||
|
||||
namespace async_simple::ranges {
|
||||
|
||||
template <class R, class Alloc = std::allocator<std::byte>>
|
||||
using elements_of = std::ranges::elements_of<R, Alloc>;
|
||||
|
||||
} // namespace async_simple::ranges
|
||||
|
||||
#else
|
||||
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <ranges>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/coro/PromiseAllocator.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple::ranges {
|
||||
|
||||
// For internal use only, for compatibility with lower version standard
|
||||
// libraries
|
||||
namespace internal {
|
||||
|
||||
// clang-format off
|
||||
template <typename T>
|
||||
concept range = requires(T& t) {
|
||||
std::ranges::begin(t);
|
||||
std::ranges::end(t);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using iterator_t = decltype(std::ranges::begin(std::declval<T&>()));
|
||||
|
||||
template <range R>
|
||||
using sentinel_t = decltype(std::ranges::end(std::declval<R&>()));
|
||||
|
||||
template <range R>
|
||||
using range_value_t = std::iter_value_t<iterator_t<R>>;
|
||||
|
||||
template <range R>
|
||||
using range_reference_t = std::iter_reference_t<iterator_t<R>>;
|
||||
|
||||
template <class T>
|
||||
concept input_range = range<T> && std::input_iterator<iterator_t<T>>;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// clang-format on
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define EMPTY_BASES __declspec(empty_bases)
|
||||
#ifdef __clang__
|
||||
#define NO_UNIQUE_ADDRESS
|
||||
#else
|
||||
#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
|
||||
#endif
|
||||
#else
|
||||
#define EMPTY_BASES
|
||||
#define NO_UNIQUE_ADDRESS [[no_unique_address]]
|
||||
#endif
|
||||
|
||||
template <class R, class Alloc = std::allocator<std::byte>>
|
||||
struct elements_of {
|
||||
NO_UNIQUE_ADDRESS R range;
|
||||
NO_UNIQUE_ADDRESS Alloc allocator{};
|
||||
};
|
||||
|
||||
template <class R, class Alloc = std::allocator<std::byte>>
|
||||
elements_of(R&&, Alloc = {}) -> elements_of<R&&, Alloc>;
|
||||
|
||||
} // namespace async_simple::ranges
|
||||
|
||||
namespace async_simple::coro::detail {
|
||||
|
||||
template <class yield>
|
||||
struct gen_promise_base;
|
||||
|
||||
} // namespace async_simple::coro::detail
|
||||
|
||||
namespace async_simple::coro {
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* Implementation comes from [P2502R2]
|
||||
* (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf)
|
||||
*
|
||||
* Generator: Synchronous Coroutine Generator for Ranges.
|
||||
* Synchronous generators are an important use case for coroutines.
|
||||
* The Generator class represents a sequence of synchronously produced
|
||||
* values where the values are produced by a coroutine. Generator is a
|
||||
* move-only view which models input_range and has move-only iterators.
|
||||
* This is because the coroutine state is a unique resource (even if the
|
||||
* coroutine handle is copyable).
|
||||
*
|
||||
* Values are produced by using the 'co_yield' keyword.
|
||||
* Using the 'co_await' keyword is not allowed In the body of Generator.
|
||||
* The end of the sequence is indicated by executing 'co_return;' either
|
||||
* explicitly or by letting execution run off the end of the coroutine.
|
||||
* For example:
|
||||
*
|
||||
* Generator<int> answer() {
|
||||
* co_yield 42;
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* Generator has 3 template parameters: Generator<Ref, V = void, Allocator = void>
|
||||
* From Ref and V, we derive types:
|
||||
* using value = conditional_t<is_void_v<V>, remove_cvref_t<Ref>, V>;
|
||||
* using reference = conditional_t<is_void_v<V>, Ref&&, Ref>;
|
||||
* using yielded = conditional_t<is_reference_v<reference>, reference, const reference&>;
|
||||
*
|
||||
* • value is a cv-unqualified object type that specifies the value type of the
|
||||
* generator’s range and iterators
|
||||
* • reference specifies the reference type (not necessarily a core language
|
||||
* reference type) of the generator’s range and iterators, and it is the return
|
||||
* value of the opeartor* member function of the generator's iterators.
|
||||
* • yielded (necessarily a reference type) is the type of the parameter to the
|
||||
* primary overload of yield_value in the generator’s associated promise type.
|
||||
*
|
||||
* Generator<meow>
|
||||
* Our expectation is that 98% of use cases will need to specify only one
|
||||
* parameter. The resulting Generator:
|
||||
* • has a value type of std::remove_cvref<meow>
|
||||
* • has a reference type of meow, if it is reference type, or meow&& otherwise
|
||||
* • has a yielded type of reference, because `std::is_reference_v<reference>`
|
||||
* must be established at this time, but users don't need to care about this.
|
||||
*
|
||||
* // Following examples show difference between:
|
||||
//
|
||||
// If I co_yield a... reference value
|
||||
// X / X&& | X& | const X&
|
||||
// ------------+------------+-----------
|
||||
// - Generator<X> (same as Generator<X&&>) X&& X
|
||||
// - Generator<const X&> ref | ref | ref const X& const X
|
||||
// - Generator<X&&> ref | 1 copy | 1 copy X&& X
|
||||
// - Generator<X&> ill-formed | ref | ill-formed X& X
|
||||
//
|
||||
*
|
||||
* Generator<int> (same as Generator<int&&>)
|
||||
* Generator<int&&> xvalue_example() {
|
||||
* [1] co_yield 1;
|
||||
* [2] int x{2};
|
||||
* [3] co_yield x; // well-formed: generated element is copy of lvalue
|
||||
* [4] const int y{3};
|
||||
* [5] co_yield y; // same as above
|
||||
* [6] int z{4};
|
||||
* [7] co_yield std::move(z); // pass by rvalue reference.
|
||||
* }
|
||||
*
|
||||
* // Generator<int>::iterator operator* return type:
|
||||
* // -> Generator<int>::reference -> int&&
|
||||
*
|
||||
* for (auto&& i : xvalue_example()) { // auto -> int&&, no copy, no move
|
||||
* std::cout << i << std::endl; // The variable z can still be used
|
||||
* // after the sequence number 7
|
||||
* }
|
||||
*
|
||||
* for (auto i : xvalue_example()) { // auto -> int
|
||||
* std::cout << i << std::endl; // The variable z cannot be used
|
||||
* // after the sequence number 7,
|
||||
* // because it is moved. But the
|
||||
* // variable x can still be used
|
||||
* // after the sequence number 3,
|
||||
* // because the value returned by
|
||||
* // the iterator is the copied.
|
||||
* }
|
||||
*
|
||||
* // Pass by reference, no copy.
|
||||
* Generator<const int&> const_lvalue_example() {
|
||||
* co_yield 1; // OK
|
||||
* const int x{2};
|
||||
* co_yield x; // OK
|
||||
* co_yield std::move(x); // OK: same as above
|
||||
* int y{3};
|
||||
* co_yield y; // OK
|
||||
* }
|
||||
*
|
||||
* Geneartor<int&> lvalue_example() {
|
||||
* co_yield 1; // ill-formed: prvalue -> non-const lvalue
|
||||
* int x{2};
|
||||
* co_yield x; // OK
|
||||
* co_yield std::move(x); // ill-formed: xvalue -> non-const lvalue
|
||||
* const int y{3};
|
||||
* co_yield y; // ill-formed: const lvalue -> non-const lvalue
|
||||
* }
|
||||
*
|
||||
* Generator<meow, woof>
|
||||
* For the rare user who needs generator to step outside the box and use a
|
||||
* proxy reference type, or who needs to generate a range whose iterators
|
||||
* yield prvalues for whatever reason, we have two-argument generator.
|
||||
* If woof is void, this is generator<meow>. Otherwise, the resulting generator:
|
||||
*
|
||||
* • has a value type of woof
|
||||
* • has a reference type of meow
|
||||
* // TODO: ...
|
||||
*
|
||||
* For example:
|
||||
* // value_type = std::string_view
|
||||
* // reference = std::string_view
|
||||
* // Generator::iterator operator* return type: std::string_view
|
||||
* // This can be expensive for types that are expensive to copy,
|
||||
* // but can provide a small performance win for types that are cheap to copy
|
||||
* // (like built-in integer types).
|
||||
* Generator<std::string_view, std::string_view> string_views() {
|
||||
* co_yield "foo";
|
||||
* co_yield "bar";
|
||||
* }
|
||||
*
|
||||
* // value_type = std::string
|
||||
* // reference = std::string_view
|
||||
* Generator<std::string_view, std::string> strings() {
|
||||
* co_yield "start";
|
||||
* std::string s;
|
||||
* for (auto sv : string_views()) {
|
||||
* s = sv;
|
||||
* s.push_back('!');
|
||||
* co_yield s;
|
||||
* }
|
||||
* co_yield "end";
|
||||
* }
|
||||
*
|
||||
* // conversion to a vector of strings
|
||||
* // If the value_type was string_view, it would convert to a vector of string_view,
|
||||
* // which would lead to undefined behavior operating on elements of v that were
|
||||
* // invalidated while iterating through the generator.
|
||||
* auto v = std::ranges::to<vector>(strings());
|
||||
*
|
||||
* Allocator support: See async_simple/coro/PromiseAllocator.h
|
||||
*
|
||||
* Recursive generator
|
||||
* A "recursive generator" is a coroutine that supports the ability to directly co_yield
|
||||
* a generator of the same type as a way of emitting the elements of that generator as
|
||||
* elements of the current generator.
|
||||
*
|
||||
* Example: A generator can co_yield other generators of the same type
|
||||
*
|
||||
* Generator<const std::string&> delete_rows(std::string table, std::vector<int> ids) {
|
||||
* for (int id : ids) {
|
||||
* co_yield std::format("DELETE FROM {0} WHERE id = {1};", table, id);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Generator<const std::string&> all_queries() {
|
||||
* co_yield std::ranges::elements_of(delete_rows("user", {4, 7, 9 10}));
|
||||
* co_yield std::ranges::elements_of(delete_rows("order", {11, 19}));
|
||||
* }
|
||||
*
|
||||
* Example: A generator can also be used recursively
|
||||
*
|
||||
* Generator<int, int> visit(TreeNode& tree) {
|
||||
* if (tree.left)
|
||||
* co_yield ranges::elements_of{visit(*tree.left)};
|
||||
* co_yield tree.value;
|
||||
* if (tree.right)
|
||||
* co_yield ranges::elements_of{visit(*tree.right)};
|
||||
* }
|
||||
*
|
||||
* In addition to being more concise, the ability to directly yield a nested generator
|
||||
* has some performance benefits compared to iterating over the contents of the nested
|
||||
* generator and manually yielding each of its elements.
|
||||
*
|
||||
* Yielding a nested generator allows the consumer of the top-level coroutine to directly
|
||||
* resume the current leaf generator when incrementing the iterator, whereas a solution
|
||||
* that has each generator manually iterating over elements of the child generator requires
|
||||
* O(depth) coroutine resumptions/suspensions per element of the sequence.
|
||||
*
|
||||
* Example: Non-recursive form incurs O(depth) resumptions/suspensions per element
|
||||
* and is more cumbersome to write:
|
||||
*
|
||||
* Generator<int, int> slow_visit(TreeNode& tree) {
|
||||
* if (tree.left) {
|
||||
* for (int x : slow_visit(*tree.left))
|
||||
* co_yield x;
|
||||
* }
|
||||
* co_yield tree.value;
|
||||
* if (tree.right) {
|
||||
* for (int x : slow_visit(*tree.right))
|
||||
* co_yield x;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
// clang-format on
|
||||
template <class Ref, class V = void, class Allocator = void>
|
||||
class Generator {
|
||||
private:
|
||||
using value =
|
||||
std::conditional_t<std::is_void_v<V>, std::remove_cvref_t<Ref>, V>;
|
||||
using reference = std::conditional_t<std::is_void_v<V>, Ref&&, Ref>;
|
||||
|
||||
class iterator;
|
||||
|
||||
// clang-format off
|
||||
static_assert(
|
||||
std::same_as<std::remove_cvref_t<value>, value> &&
|
||||
std::is_object_v<value>,
|
||||
"generator's value type must be a cv-unqualified object type");
|
||||
static_assert(std::is_reference_v<reference> ||
|
||||
(std::is_object_v<reference> &&
|
||||
std::same_as<std::remove_cv_t<reference>, reference> &&
|
||||
std::copy_constructible<reference>),
|
||||
"generator's second argument must be a reference type or a "
|
||||
"cv-unqualified "
|
||||
"copy-constructible object type");
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
class promise_type;
|
||||
|
||||
using Handle = std::coroutine_handle<promise_type>;
|
||||
using yielded = std::conditional_t<std::is_reference_v<reference>,
|
||||
reference, const reference&>;
|
||||
|
||||
Generator(Generator&& other) noexcept
|
||||
: _coro(std::exchange(other._coro, nullptr)) {}
|
||||
Generator& operator=(Generator&& other) noexcept;
|
||||
~Generator();
|
||||
Generator(const Generator&) = delete;
|
||||
Generator& operator=(const Generator&) = delete;
|
||||
[[nodiscard]] iterator begin();
|
||||
[[nodiscard]] std::default_sentinel_t end() const noexcept { return {}; }
|
||||
|
||||
private:
|
||||
explicit Generator(Handle coro) noexcept;
|
||||
|
||||
private:
|
||||
Handle _coro = nullptr;
|
||||
|
||||
template <class Yield>
|
||||
friend struct detail::gen_promise_base;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class yielded>
|
||||
struct gen_promise_base {
|
||||
protected:
|
||||
struct NestInfo {
|
||||
std::exception_ptr except_;
|
||||
std::coroutine_handle<gen_promise_base> parent_;
|
||||
std::coroutine_handle<gen_promise_base> root_;
|
||||
};
|
||||
|
||||
template <class R2, class V2, class Alloc2>
|
||||
struct NestedAwaiter {
|
||||
NestInfo nested_;
|
||||
Generator<R2, V2, Alloc2> gen_;
|
||||
|
||||
explicit NestedAwaiter(Generator<R2, V2, Alloc2>&& gen) noexcept
|
||||
: gen_(std::move(gen)) {}
|
||||
bool await_ready() noexcept { return !gen_._coro; }
|
||||
|
||||
template <class Promise>
|
||||
std::coroutine_handle<> await_suspend(
|
||||
std::coroutine_handle<Promise> handle) noexcept {
|
||||
auto target = std::coroutine_handle<gen_promise_base>::from_address(
|
||||
gen_._coro.address());
|
||||
nested_.parent_ =
|
||||
std::coroutine_handle<gen_promise_base>::from_address(
|
||||
handle.address());
|
||||
auto& current = handle.promise();
|
||||
if (current.info_) {
|
||||
nested_.root_ = current.info_->root_;
|
||||
} else {
|
||||
nested_.root_ = nested_.parent_;
|
||||
}
|
||||
nested_.root_.promise().top_ = target;
|
||||
target.promise().info_ = std::addressof(nested_);
|
||||
return target;
|
||||
}
|
||||
|
||||
void await_resume() {
|
||||
if (nested_.except_) {
|
||||
std::rethrow_exception(std::move(nested_.except_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ElementAwaiter {
|
||||
std::remove_cvref_t<yielded> value_;
|
||||
constexpr bool await_ready() const noexcept { return false; }
|
||||
|
||||
template <class Promise>
|
||||
constexpr void await_suspend(
|
||||
std::coroutine_handle<Promise> handle) noexcept {
|
||||
auto& current = handle.promise();
|
||||
current.value_ = std::addressof(value_);
|
||||
}
|
||||
|
||||
constexpr void await_resume() const noexcept {}
|
||||
};
|
||||
|
||||
struct FinalAwaiter {
|
||||
bool await_ready() const noexcept { return false; }
|
||||
|
||||
template <class Promise>
|
||||
std::coroutine_handle<> await_suspend(
|
||||
std::coroutine_handle<Promise> handle) noexcept {
|
||||
auto& current = handle.promise();
|
||||
|
||||
if (!current.info_) {
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
auto previous = current.info_->parent_;
|
||||
current.info_->root_.promise().top_ = previous;
|
||||
current.info_ = nullptr;
|
||||
return previous;
|
||||
}
|
||||
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
template <class Ref, class V, class Alloc>
|
||||
friend class async_simple::coro::Generator;
|
||||
|
||||
std::add_pointer_t<yielded> value_ = nullptr;
|
||||
std::coroutine_handle<gen_promise_base> top_ =
|
||||
std::coroutine_handle<gen_promise_base>::from_promise(*this);
|
||||
NestInfo* info_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
class Generator<Ref, V, Allocator>::promise_type
|
||||
: public detail::gen_promise_base<yielded>,
|
||||
public detail::PromiseAllocator<Allocator> {
|
||||
public:
|
||||
using Base = detail::gen_promise_base<yielded>;
|
||||
|
||||
std::suspend_always initial_suspend() noexcept { return {}; }
|
||||
// When info is `nullptr`, equivalent to std::suspend_always
|
||||
auto final_suspend() noexcept { return typename Base::FinalAwaiter{}; }
|
||||
|
||||
std::suspend_always yield_value(yielded val) noexcept {
|
||||
this->value_ = std::addressof(val);
|
||||
return {};
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
auto yield_value(const std::remove_reference_t<yielded>& lval) requires
|
||||
std::is_rvalue_reference_v<yielded> && std::constructible_from<
|
||||
std::remove_cvref_t<yielded>, const std::remove_reference_t<yielded>& >
|
||||
{ return typename Base::ElementAwaiter{lval}; }
|
||||
// clang-format on
|
||||
|
||||
// clang-format off
|
||||
template <class R2, class V2, class Alloc2, class Unused>
|
||||
requires std::same_as<typename Generator<R2, V2, Alloc2>::yielded, yielded>
|
||||
auto yield_value(
|
||||
ranges::elements_of<Generator<R2, V2, Alloc2>&&, Unused> g) noexcept {
|
||||
// clang-format on
|
||||
return typename Base::template NestedAwaiter<R2, V2, Alloc2>{
|
||||
std::move(g.range)};
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
template <class R, class Alloc>
|
||||
requires std::convertible_to<ranges::internal::range_reference_t<R>, yielded>
|
||||
auto yield_value(ranges::elements_of<R, Alloc> r) noexcept {
|
||||
// clang-format on
|
||||
auto nested = [](std::allocator_arg_t, Alloc,
|
||||
ranges::internal::iterator_t<R> i,
|
||||
ranges::internal::sentinel_t<R> s)
|
||||
-> Generator<yielded, ranges::internal::range_value_t<R>, Alloc> {
|
||||
for (; i != s; ++i) {
|
||||
co_yield static_cast<yielded>(*i);
|
||||
}
|
||||
};
|
||||
return yield_value(ranges::elements_of{
|
||||
nested(std::allocator_arg, r.allocator, std::ranges::begin(r.range),
|
||||
std::ranges::end(r.range))});
|
||||
}
|
||||
|
||||
void return_void() noexcept {}
|
||||
void await_transform() = delete;
|
||||
void unhandled_exception() {
|
||||
if (this->info_) {
|
||||
this->info_->except_ = std::current_exception();
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Generator get_return_object() noexcept {
|
||||
return Generator(Generator::Handle::from_promise(*this));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
class Generator<Ref, V, Allocator>::iterator {
|
||||
public:
|
||||
using value_type = value;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
explicit iterator(Handle coro) noexcept : _coro(coro) {}
|
||||
~iterator() {
|
||||
if (_coro) {
|
||||
if (!_coro.done()) {
|
||||
// TODO: error log
|
||||
}
|
||||
_coro.destroy();
|
||||
_coro = nullptr;
|
||||
}
|
||||
}
|
||||
iterator(iterator&& rhs) noexcept
|
||||
: _coro(std::exchange(rhs._coro, nullptr)) {}
|
||||
|
||||
iterator& operator=(iterator&& rhs) {
|
||||
logicAssert(!_coro, "Should not own a coroutine handle");
|
||||
_coro = std::exchange(rhs._coro, nullptr);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return _coro && !_coro.done(); }
|
||||
|
||||
[[nodiscard]] bool operator==(std::default_sentinel_t) const {
|
||||
return !_coro || _coro.done();
|
||||
}
|
||||
|
||||
reference operator*() const {
|
||||
logicAssert(
|
||||
this->operator bool(),
|
||||
"Should have a coroutine handle or the coroutine has not ended");
|
||||
assert(_coro.promise().top_.promise().value_ &&
|
||||
"value pointer is nullptr");
|
||||
return static_cast<yielded>(*_coro.promise().top_.promise().value_);
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
logicAssert(this->operator bool(),
|
||||
"Can't increment generator end iterator");
|
||||
_coro.promise().top_.resume();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator++(int) { ++(*this); }
|
||||
|
||||
private:
|
||||
Handle _coro = nullptr;
|
||||
};
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
Generator<Ref, V, Allocator>::Generator(Handle coro) noexcept : _coro(coro) {}
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
Generator<Ref, V, Allocator>& Generator<Ref, V, Allocator>::operator=(
|
||||
Generator&& other) noexcept {
|
||||
if (_coro) {
|
||||
if (!_coro.done()) {
|
||||
// TODO: [Warning] the coroutine is not done!
|
||||
}
|
||||
_coro.destroy();
|
||||
}
|
||||
_coro = std::exchange(other._coro, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
Generator<Ref, V, Allocator>::~Generator() {
|
||||
if (_coro) {
|
||||
if (!_coro.done()) {
|
||||
// TODO: log
|
||||
}
|
||||
_coro.destroy();
|
||||
_coro = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
typename Generator<Ref, V, Allocator>::iterator
|
||||
Generator<Ref, V, Allocator>::begin() {
|
||||
logicAssert(!!_coro, "Can't call begin on moved-from generator");
|
||||
_coro.resume();
|
||||
return iterator(std::exchange(_coro, nullptr));
|
||||
}
|
||||
|
||||
} // namespace async_simple::coro
|
||||
|
||||
template <class Ref, class V, class Allocator>
|
||||
inline constexpr bool
|
||||
std::ranges::enable_view<async_simple::coro::Generator<Ref, V, Allocator>> =
|
||||
true;
|
||||
|
||||
#endif // __has_include(<generator>)
|
||||
|
||||
#endif // ASYNC_SIMPLE_CORO_GENERATOR_H
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ASYNC_SIMPLE_CORO_LATCH_H
|
||||
#define ASYNC_SIMPLE_CORO_LATCH_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include "async_simple/coro/ConditionVariable.h"
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
#include "async_simple/coro/Mutex.h"
|
||||
#include "async_simple/coro/SpinLock.h"
|
||||
|
||||
namespace async_simple::coro {
|
||||
|
||||
// The latch class is a downward counter of type std::size_t which can be
|
||||
// used to synchronize coroutines. The value of the counter is initialized on
|
||||
// creation. Coroutines may block on the latch until the counter is decremented
|
||||
// to zero. It will suspend the current coroutine and switch to other coroutines
|
||||
// to run.
|
||||
// There is no possibility to increase or reset the counter, which
|
||||
// makes the latch a single-use barrier.
|
||||
class Latch {
|
||||
public:
|
||||
explicit Latch(std::size_t count) : count_(count) {}
|
||||
~Latch() = default;
|
||||
Latch(const Latch&) = delete;
|
||||
Latch& operator=(const Latch&) = delete;
|
||||
|
||||
// decrements the counter in a non-blocking manner
|
||||
Lazy<void> count_down(std::size_t update = 1) {
|
||||
auto lk = co_await mutex_.coScopedLock();
|
||||
assert(count_ >= update);
|
||||
count_ -= update;
|
||||
if (!count_) {
|
||||
cv_.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// tests if the internal counter equals zero
|
||||
Lazy<bool> try_wait() const noexcept {
|
||||
auto lk = co_await mutex_.coScopedLock();
|
||||
co_return !count_;
|
||||
}
|
||||
|
||||
// blocks until the counter reaches zero
|
||||
// If the counter is not 0, the current coroutine will be suspended
|
||||
Lazy<void> wait() const noexcept {
|
||||
auto lk = co_await mutex_.coScopedLock();
|
||||
co_await cv_.wait(mutex_, [&] { return count_ == 0; });
|
||||
}
|
||||
|
||||
// decrements the counter and blocks until it reaches zero
|
||||
Lazy<void> arrive_and_wait(std::size_t update = 1) noexcept {
|
||||
co_await count_down(update);
|
||||
co_await wait();
|
||||
}
|
||||
|
||||
private:
|
||||
using MutexType = SpinLock;
|
||||
|
||||
mutable MutexType mutex_;
|
||||
mutable ConditionVariable<MutexType> cv_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
} // namespace async_simple::coro
|
||||
|
||||
#endif
|
|
@ -16,16 +16,16 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_LAZY_H
|
||||
#define ASYNC_SIMPLE_CORO_LAZY_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <async_simple/coro/DetachedCoroutine.h>
|
||||
#include <async_simple/coro/ViaCoroutine.h>
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <variant>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Try.h"
|
||||
#include "async_simple/coro/DetachedCoroutine.h"
|
||||
#include "async_simple/coro/ViaCoroutine.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
@ -48,9 +48,12 @@ class Lazy;
|
|||
struct Yield {};
|
||||
|
||||
namespace detail {
|
||||
template <typename LazyType, typename IAlloc, typename OAlloc, bool Para>
|
||||
template <class, typename OAlloc, bool Para>
|
||||
struct CollectAllAwaiter;
|
||||
|
||||
template <bool Para, template <typename> typename LazyType, typename... Ts>
|
||||
struct CollectAllVariadicAwaiter;
|
||||
|
||||
template <typename LazyType, typename IAlloc>
|
||||
struct CollectAnyAwaiter;
|
||||
|
||||
|
@ -62,122 +65,125 @@ struct CollectAnyVariadicAwaiter;
|
|||
namespace detail {
|
||||
|
||||
class LazyPromiseBase {
|
||||
public:
|
||||
// Resume the caller waiting to the current coroutine. Note that we need
|
||||
// destroy the frame for the current coroutine explicitly. Since after
|
||||
// FinalAwaiter, The current coroutine should be suspended and never to
|
||||
// resume. So that we couldn't expect it to release it self any more.
|
||||
struct FinalAwaiter {
|
||||
bool await_ready() const noexcept { return false; }
|
||||
template <typename PromiseType>
|
||||
auto await_suspend(std::coroutine_handle<PromiseType> h) noexcept {
|
||||
return h.promise()._continuation;
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
public:
|
||||
// Resume the caller waiting to the current coroutine. Note that we need
|
||||
// destroy the frame for the current coroutine explicitly. Since after
|
||||
// FinalAwaiter, The current coroutine should be suspended and never to
|
||||
// resume. So that we couldn't expect it to release it self any more.
|
||||
struct FinalAwaiter {
|
||||
bool await_ready() const noexcept { return false; }
|
||||
template <typename PromiseType>
|
||||
auto await_suspend(std::coroutine_handle<PromiseType> h) noexcept {
|
||||
return h.promise()._continuation;
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
struct YieldAwaiter {
|
||||
YieldAwaiter(Executor* executor) : _executor(executor) {}
|
||||
bool await_ready() const noexcept { return false; }
|
||||
void await_suspend(std::coroutine_handle<> handle) {
|
||||
logicAssert(_executor, "Yielding is only meaningful with an executor!");
|
||||
std::function<void()> func = [h = std::move(handle)]() mutable {
|
||||
h.resume();
|
||||
};
|
||||
_executor->schedule(func);
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
struct YieldAwaiter {
|
||||
YieldAwaiter(Executor* executor) : _executor(executor) {}
|
||||
bool await_ready() const noexcept { return false; }
|
||||
void await_suspend(std::coroutine_handle<> handle) {
|
||||
logicAssert(_executor,
|
||||
"Yielding is only meaningful with an executor!");
|
||||
std::function<void()> func = [h = std::move(handle)]() mutable {
|
||||
h.resume();
|
||||
};
|
||||
_executor->schedule(func);
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
|
||||
private:
|
||||
private:
|
||||
Executor* _executor;
|
||||
};
|
||||
|
||||
public:
|
||||
LazyPromiseBase() : _executor(nullptr) {}
|
||||
// Lazily started, coroutine will not execute until first resume() is called
|
||||
std::suspend_always initial_suspend() noexcept { return {}; }
|
||||
FinalAwaiter final_suspend() noexcept { return {}; }
|
||||
|
||||
template <typename Awaitable>
|
||||
auto await_transform(Awaitable&& awaitable) {
|
||||
// See CoAwait.h for details.
|
||||
return detail::coAwait(_executor, std::forward<Awaitable>(awaitable));
|
||||
}
|
||||
|
||||
auto await_transform(CurrentExecutor) {
|
||||
return ReadyAwaiter<Executor*>(_executor);
|
||||
}
|
||||
auto await_transform(Yield) { return YieldAwaiter(_executor); }
|
||||
|
||||
/// IMPORTANT: _continuation should be the first member due to the
|
||||
/// requirement of dbg script.
|
||||
std::coroutine_handle<> _continuation;
|
||||
Executor* _executor;
|
||||
};
|
||||
|
||||
public:
|
||||
LazyPromiseBase() : _executor(nullptr) {}
|
||||
// Lazily started, coroutine will not execute until first resume() is called
|
||||
std::suspend_always initial_suspend() noexcept { return {}; }
|
||||
FinalAwaiter final_suspend() noexcept { return {}; }
|
||||
|
||||
template <typename Awaitable>
|
||||
auto await_transform(Awaitable&& awaitable) {
|
||||
// See CoAwait.h for details.
|
||||
return detail::coAwait(_executor, std::forward<Awaitable>(awaitable));
|
||||
}
|
||||
|
||||
auto await_transform(CurrentExecutor) {
|
||||
return ReadyAwaiter<Executor*>(_executor);
|
||||
}
|
||||
auto await_transform(Yield) { return YieldAwaiter(_executor); }
|
||||
|
||||
/// IMPORTANT: _continuation should be the first member due to the
|
||||
/// requirement of dbg script.
|
||||
std::coroutine_handle<> _continuation;
|
||||
Executor* _executor;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class LazyPromise : public LazyPromiseBase {
|
||||
public:
|
||||
LazyPromise() noexcept {}
|
||||
~LazyPromise() noexcept {}
|
||||
public:
|
||||
LazyPromise() noexcept {}
|
||||
~LazyPromise() noexcept {}
|
||||
|
||||
Lazy<T> get_return_object() noexcept;
|
||||
Lazy<T> get_return_object() noexcept;
|
||||
|
||||
template <typename V,
|
||||
typename = std::enable_if_t<std::is_convertible_v<V&&, T>>>
|
||||
void return_value(V&& value) noexcept(
|
||||
std::is_nothrow_constructible_v<T, V&&>) {
|
||||
_value.template emplace<T>(std::forward<V>(value));
|
||||
}
|
||||
void unhandled_exception() noexcept {
|
||||
_value.template emplace<std::exception_ptr>(std::current_exception());
|
||||
}
|
||||
|
||||
public:
|
||||
T& result() & {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(_value));
|
||||
}
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return std::get<T>(_value);
|
||||
}
|
||||
T&& result() && {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(_value));
|
||||
}
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return std::move(std::get<T>(_value));
|
||||
}
|
||||
|
||||
Try<T> tryResult() noexcept {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY { return Try<T>(std::get<std::exception_ptr>(_value)); }
|
||||
else {
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return Try<T>(std::move(std::get<T>(_value)));
|
||||
template <typename V>
|
||||
void return_value(V&& value) noexcept(
|
||||
std::is_nothrow_constructible_v<
|
||||
T, V&&>) requires std::is_convertible_v<V&&, T> {
|
||||
_value.template emplace<T>(std::forward<V>(value));
|
||||
}
|
||||
void unhandled_exception() noexcept {
|
||||
_value.template emplace<std::exception_ptr>(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<std::monostate, T, std::exception_ptr> _value;
|
||||
public:
|
||||
T& result() & {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(_value));
|
||||
}
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return std::get<T>(_value);
|
||||
}
|
||||
T&& result() && {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY {
|
||||
std::rethrow_exception(std::get<std::exception_ptr>(_value));
|
||||
}
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return std::move(std::get<T>(_value));
|
||||
}
|
||||
|
||||
Try<T> tryResult() noexcept {
|
||||
if (std::holds_alternative<std::exception_ptr>(_value))
|
||||
AS_UNLIKELY { return Try<T>(std::get<std::exception_ptr>(_value)); }
|
||||
else {
|
||||
assert(std::holds_alternative<T>(_value));
|
||||
return Try<T>(std::move(std::get<T>(_value)));
|
||||
}
|
||||
}
|
||||
|
||||
std::variant<std::monostate, T, std::exception_ptr> _value;
|
||||
};
|
||||
|
||||
template <>
|
||||
class LazyPromise<void> : public LazyPromiseBase {
|
||||
public:
|
||||
Lazy<void> get_return_object() noexcept;
|
||||
void return_void() noexcept {}
|
||||
void unhandled_exception() noexcept { _exception = std::current_exception(); }
|
||||
public:
|
||||
Lazy<void> get_return_object() noexcept;
|
||||
void return_void() noexcept {}
|
||||
void unhandled_exception() noexcept {
|
||||
_exception = std::current_exception();
|
||||
}
|
||||
|
||||
void result() {
|
||||
if (_exception != nullptr)
|
||||
AS_UNLIKELY { std::rethrow_exception(_exception); }
|
||||
}
|
||||
Try<void> tryResult() noexcept { return Try<void>(_exception); }
|
||||
void result() {
|
||||
if (_exception != nullptr)
|
||||
AS_UNLIKELY { std::rethrow_exception(_exception); }
|
||||
}
|
||||
Try<void> tryResult() noexcept { return Try<void>(_exception); }
|
||||
|
||||
public:
|
||||
std::exception_ptr _exception{nullptr};
|
||||
public:
|
||||
std::exception_ptr _exception{nullptr};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
@ -189,152 +195,161 @@ namespace detail {
|
|||
|
||||
template <typename T>
|
||||
struct LazyAwaiterBase {
|
||||
using Handle = CoroHandle<detail::LazyPromise<T>>;
|
||||
Handle _handle;
|
||||
using Handle = CoroHandle<detail::LazyPromise<T>>;
|
||||
Handle _handle;
|
||||
|
||||
LazyAwaiterBase(LazyAwaiterBase& other) = delete;
|
||||
LazyAwaiterBase& operator=(LazyAwaiterBase& other) = delete;
|
||||
LazyAwaiterBase(LazyAwaiterBase& other) = delete;
|
||||
LazyAwaiterBase& operator=(LazyAwaiterBase& other) = delete;
|
||||
|
||||
LazyAwaiterBase(LazyAwaiterBase&& other)
|
||||
: _handle(std::exchange(other._handle, nullptr)) {}
|
||||
LazyAwaiterBase(LazyAwaiterBase&& other)
|
||||
: _handle(std::exchange(other._handle, nullptr)) {}
|
||||
|
||||
LazyAwaiterBase& operator=(LazyAwaiterBase&& other) {
|
||||
std::swap(_handle, other._handle);
|
||||
return *this;
|
||||
}
|
||||
|
||||
LazyAwaiterBase(Handle coro) : _handle(coro) {}
|
||||
~LazyAwaiterBase() {
|
||||
if (_handle) {
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
LazyAwaiterBase& operator=(LazyAwaiterBase&& other) {
|
||||
std::swap(_handle, other._handle);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
bool await_ready() const noexcept { return false; }
|
||||
LazyAwaiterBase(Handle coro) : _handle(coro) {}
|
||||
~LazyAwaiterBase() {
|
||||
if (_handle) {
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T2 = T, std::enable_if_t<std::is_void_v<T2>, int> = 0>
|
||||
void awaitResume() {
|
||||
_handle.promise().result();
|
||||
// We need to destroy the handle expclictly since the awaited coroutine
|
||||
// after symmetric transfer couldn't release it self any more.
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
}
|
||||
bool await_ready() const noexcept { return false; }
|
||||
|
||||
template <typename T2 = T, std::enable_if_t<!std::is_void_v<T2>, int> = 0>
|
||||
T awaitResume() {
|
||||
auto r = std::move(_handle.promise()).result();
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
return r;
|
||||
}
|
||||
auto awaitResume() {
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
_handle.promise().result();
|
||||
// We need to destroy the handle expclictly since the awaited
|
||||
// coroutine after symmetric transfer couldn't release it self any
|
||||
// more.
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
} else {
|
||||
auto r = std::move(_handle.promise()).result();
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
Try<T> awaitResumeTry() noexcept {
|
||||
Try<T> ret = std::move(_handle.promise().tryResult());
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
return ret;
|
||||
}
|
||||
Try<T> awaitResumeTry() noexcept {
|
||||
Try<T> ret = _handle.promise().tryResult();
|
||||
_handle.destroy();
|
||||
_handle = nullptr;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool reschedule>
|
||||
class LazyBase {
|
||||
public:
|
||||
using promise_type = detail::LazyPromise<T>;
|
||||
using Handle = CoroHandle<promise_type>;
|
||||
using ValueType = T;
|
||||
public:
|
||||
using promise_type = detail::LazyPromise<T>;
|
||||
using Handle = CoroHandle<promise_type>;
|
||||
using ValueType = T;
|
||||
|
||||
struct AwaiterBase : public detail::LazyAwaiterBase<T> {
|
||||
using Base = detail::LazyAwaiterBase<T>;
|
||||
AwaiterBase(Handle coro) : Base(coro) {}
|
||||
struct AwaiterBase : public detail::LazyAwaiterBase<T> {
|
||||
using Base = detail::LazyAwaiterBase<T>;
|
||||
AwaiterBase(Handle coro) : Base(coro) {}
|
||||
|
||||
AS_INLINE auto await_suspend(
|
||||
std::coroutine_handle<> continuation) noexcept {
|
||||
// current coro started, caller becomes my continuation
|
||||
this->_handle.promise()._continuation = continuation;
|
||||
AS_INLINE auto await_suspend(
|
||||
std::coroutine_handle<> continuation) noexcept(!reschedule) {
|
||||
// current coro started, caller becomes my continuation
|
||||
this->_handle.promise()._continuation = continuation;
|
||||
|
||||
using R = std::conditional_t<reschedule, void, std::coroutine_handle<>>;
|
||||
return awaitSuspendImpl<R>();
|
||||
}
|
||||
return awaitSuspendImpl();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T2 = T, std::enable_if_t<!std::is_void_v<T2>, int> = 0>
|
||||
auto awaitSuspendImpl() noexcept {
|
||||
return this->_handle;
|
||||
}
|
||||
|
||||
template <typename T2 = T, std::enable_if_t<std::is_void_v<T2>, int> = 0>
|
||||
auto awaitSuspendImpl() noexcept {
|
||||
// executor schedule performed
|
||||
auto& pr = this->_handle.promise();
|
||||
logicAssert(pr._executor, "RescheduleLazy need executor");
|
||||
pr._executor->schedule([h = this->_handle]() mutable {
|
||||
h.resume();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct TryAwaiter : public AwaiterBase {
|
||||
TryAwaiter(Handle coro) : AwaiterBase(coro) {}
|
||||
AS_INLINE Try<T> await_resume() noexcept {
|
||||
return AwaiterBase::awaitResumeTry();
|
||||
private:
|
||||
auto awaitSuspendImpl() noexcept(!reschedule) {
|
||||
if constexpr (reschedule) {
|
||||
// executor schedule performed
|
||||
auto& pr = this->_handle.promise();
|
||||
logicAssert(pr._executor, "RescheduleLazy need executor");
|
||||
pr._executor->schedule(
|
||||
[h = this->_handle]() mutable { h.resume(); });
|
||||
} else {
|
||||
return this->_handle;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct ValueAwaiter : public AwaiterBase {
|
||||
ValueAwaiter(Handle coro) : AwaiterBase(coro) {}
|
||||
AS_INLINE T await_resume() { return AwaiterBase::awaitResume(); }
|
||||
};
|
||||
struct TryAwaiter : public AwaiterBase {
|
||||
TryAwaiter(Handle coro) : AwaiterBase(coro) {}
|
||||
AS_INLINE Try<T> await_resume() noexcept {
|
||||
return AwaiterBase::awaitResumeTry();
|
||||
};
|
||||
|
||||
~LazyBase() {
|
||||
if (_coro) {
|
||||
_coro.destroy();
|
||||
_coro = nullptr;
|
||||
}
|
||||
};
|
||||
explicit LazyBase(Handle coro) : _coro(coro) {}
|
||||
LazyBase(LazyBase&& other) : _coro(std::move(other._coro)) {
|
||||
other._coro = nullptr;
|
||||
}
|
||||
|
||||
LazyBase(const LazyBase&) = delete;
|
||||
LazyBase& operator=(const LazyBase&) = delete;
|
||||
|
||||
Executor* getExecutor() { return _coro.promise()._executor; }
|
||||
|
||||
template <typename F>
|
||||
void start(F&& callback) {
|
||||
// callback should take a single Try<T> as parameter, return value will
|
||||
// be ignored. a detached coroutine will not suspend at initial/final
|
||||
// suspend point.
|
||||
auto launchCoro = [](LazyBase lazy,
|
||||
std::decay_t<F> cb) -> detail::DetachedCoroutine {
|
||||
cb(std::move(co_await lazy.coAwaitTry()));
|
||||
auto coAwait(Executor* ex) {
|
||||
if constexpr (reschedule) {
|
||||
logicAssert(false,
|
||||
"RescheduleLazy should be only allowed in "
|
||||
"DetachedCoroutine");
|
||||
}
|
||||
// derived lazy inherits executor
|
||||
this->_handle.promise()._executor = ex;
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
[[maybe_unused]] auto detached =
|
||||
launchCoro(std::move(*this), std::forward<F>(callback));
|
||||
}
|
||||
|
||||
bool isReady() const { return !_coro || _coro.done(); }
|
||||
struct ValueAwaiter : public AwaiterBase {
|
||||
ValueAwaiter(Handle coro) : AwaiterBase(coro) {}
|
||||
AS_INLINE T await_resume() { return AwaiterBase::awaitResume(); }
|
||||
};
|
||||
|
||||
auto operator co_await() {
|
||||
return ValueAwaiter(std::exchange(_coro, nullptr));
|
||||
}
|
||||
~LazyBase() {
|
||||
if (_coro) {
|
||||
_coro.destroy();
|
||||
_coro = nullptr;
|
||||
}
|
||||
};
|
||||
explicit LazyBase(Handle coro) : _coro(coro) {}
|
||||
LazyBase(LazyBase&& other) : _coro(std::move(other._coro)) {
|
||||
other._coro = nullptr;
|
||||
}
|
||||
|
||||
auto coAwaitTry() { return TryAwaiter(std::exchange(_coro, nullptr)); }
|
||||
LazyBase(const LazyBase&) = delete;
|
||||
LazyBase& operator=(const LazyBase&) = delete;
|
||||
|
||||
protected:
|
||||
Handle _coro;
|
||||
Executor* getExecutor() { return _coro.promise()._executor; }
|
||||
|
||||
template <typename LazyType, typename IAlloc, typename OAlloc, bool Para>
|
||||
friend struct detail::CollectAllAwaiter;
|
||||
template <typename F>
|
||||
void start(F&& callback) requires(std::is_invocable_v<F&&, Try<T>>) {
|
||||
// callback should take a single Try<T> as parameter, return value will
|
||||
// be ignored. a detached coroutine will not suspend at initial/final
|
||||
// suspend point.
|
||||
auto launchCoro = [](LazyBase lazy,
|
||||
std::decay_t<F> cb) -> detail::DetachedCoroutine {
|
||||
cb(co_await lazy.coAwaitTry());
|
||||
};
|
||||
[[maybe_unused]] auto detached =
|
||||
launchCoro(std::move(*this), std::forward<F>(callback));
|
||||
}
|
||||
|
||||
template <typename LazyType, typename IAlloc>
|
||||
friend struct detail::CollectAnyAwaiter;
|
||||
bool isReady() const { return !_coro || _coro.done(); }
|
||||
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
friend struct detail::CollectAnyVariadicAwaiter;
|
||||
auto operator co_await() {
|
||||
return ValueAwaiter(std::exchange(_coro, nullptr));
|
||||
}
|
||||
|
||||
auto coAwaitTry() { return TryAwaiter(std::exchange(_coro, nullptr)); }
|
||||
|
||||
protected:
|
||||
Handle _coro;
|
||||
|
||||
template <class, typename OAlloc, bool Para>
|
||||
friend struct detail::CollectAllAwaiter;
|
||||
|
||||
template <bool, template <typename> typename, typename...>
|
||||
friend struct detail::CollectAllVariadicAwaiter;
|
||||
|
||||
template <typename LazyType, typename IAlloc>
|
||||
friend struct detail::CollectAnyAwaiter;
|
||||
|
||||
template <template <typename> typename LazyType, typename... Ts>
|
||||
friend struct detail::CollectAnyVariadicAwaiter;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
@ -418,40 +433,40 @@ class LazyBase {
|
|||
// pass its executor instance to the awaitable.
|
||||
template <typename T = void>
|
||||
class [[nodiscard]] Lazy : public detail::LazyBase<T, /*reschedule=*/false> {
|
||||
using Base = detail::LazyBase<T, false>;
|
||||
using Base = detail::LazyBase<T, false>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
public:
|
||||
using Base::Base;
|
||||
|
||||
// Bind an executor to a Lazy, and convert it to RescheduleLazy.
|
||||
// You can only call via on rvalue, i.e. a Lazy is not accessible after
|
||||
// via() called.
|
||||
RescheduleLazy<T> via(Executor* ex) && {
|
||||
logicAssert(this->_coro.operator bool(),
|
||||
"Lazy do not have a coroutine_handle");
|
||||
this->_coro.promise()._executor = ex;
|
||||
return RescheduleLazy<T>(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
// Bind an executor to a Lazy, and convert it to RescheduleLazy.
|
||||
// You can only call via on rvalue, i.e. a Lazy is not accessible after
|
||||
// via() called.
|
||||
RescheduleLazy<T> via(Executor* ex) && {
|
||||
logicAssert(this->_coro.operator bool(),
|
||||
"Lazy do not have a coroutine_handle");
|
||||
this->_coro.promise()._executor = ex;
|
||||
return RescheduleLazy<T>(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
|
||||
// Bind an executor only. Don't re-schedule.
|
||||
//
|
||||
// Users shouldn't use `setEx` directly. `setEx` is designed
|
||||
// for internal purpose only. See uthread/Await.h/await for details.
|
||||
Lazy<T> setEx(Executor* ex) && {
|
||||
logicAssert(this->_coro.operator bool(),
|
||||
"Lazy do not have a coroutine_handle");
|
||||
this->_coro.promise()._executor = ex;
|
||||
return Lazy<T>(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
// Bind an executor only. Don't re-schedule.
|
||||
//
|
||||
// Users shouldn't use `setEx` directly. `setEx` is designed
|
||||
// for internal purpose only. See uthread/Await.h/await for details.
|
||||
Lazy<T> setEx(Executor* ex) && {
|
||||
logicAssert(this->_coro.operator bool(),
|
||||
"Lazy do not have a coroutine_handle");
|
||||
this->_coro.promise()._executor = ex;
|
||||
return Lazy<T>(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
|
||||
auto coAwait(Executor* ex) {
|
||||
// derived lazy inherits executor
|
||||
this->_coro.promise()._executor = ex;
|
||||
return typename Base::ValueAwaiter(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
auto coAwait(Executor* ex) {
|
||||
// derived lazy inherits executor
|
||||
this->_coro.promise()._executor = ex;
|
||||
return typename Base::ValueAwaiter(std::exchange(this->_coro, nullptr));
|
||||
}
|
||||
|
||||
private:
|
||||
friend class RescheduleLazy<T>;
|
||||
private:
|
||||
friend class RescheduleLazy<T>;
|
||||
};
|
||||
|
||||
// A RescheduleLazy is a Lazy with an executor. The executor of a RescheduleLazy
|
||||
|
@ -468,28 +483,34 @@ class [[nodiscard]] Lazy : public detail::LazyBase<T, /*reschedule=*/false> {
|
|||
template <typename T = void>
|
||||
class [[nodiscard]] RescheduleLazy
|
||||
: public detail::LazyBase<T, /*reschedule=*/true> {
|
||||
using Base = detail::LazyBase<T, true>;
|
||||
using Base = detail::LazyBase<T, true>;
|
||||
|
||||
public:
|
||||
void detach() {
|
||||
this->start([](auto&& t) {
|
||||
if (t.hasError()) {
|
||||
std::rethrow_exception(t.getException());
|
||||
}
|
||||
});
|
||||
}
|
||||
public:
|
||||
void detach() {
|
||||
this->start([](auto&& t) {
|
||||
if (t.hasError()) {
|
||||
std::rethrow_exception(t.getException());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
using Base::Base;
|
||||
[[deprecated(
|
||||
"RescheduleLazy should be only allowed in DetachedCoroutine")]] auto
|
||||
operator co_await() {
|
||||
return Base::operator co_await();
|
||||
}
|
||||
|
||||
private:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline Lazy<T> detail::LazyPromise<T>::get_return_object() noexcept {
|
||||
return Lazy<T>(Lazy<T>::Handle::from_promise(*this));
|
||||
return Lazy<T>(Lazy<T>::Handle::from_promise(*this));
|
||||
}
|
||||
|
||||
inline Lazy<void> detail::LazyPromise<void>::get_return_object() noexcept {
|
||||
return Lazy<void>(Lazy<void>::Handle::from_promise(*this));
|
||||
return Lazy<void>(Lazy<void>::Handle::from_promise(*this));
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO__MUTEXH
|
||||
#define ASYNC_SIMPLE_CORO__MUTEXH
|
||||
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ASYNC_SIMPLE_CORO_PROMISEALLOCATOR_H
|
||||
#define ASYNC_SIMPLE_CORO_PROMISEALLOCATOR_H
|
||||
|
||||
#include <concepts>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
// FIXME: Definition not found when clang compiles libstdcxx
|
||||
// The reason is that clang does not define the macro __cpp_sized_deallocation
|
||||
// The reason why `__cpp_sized_deallocation` is not enabled is that it will
|
||||
// cause ABI breaking
|
||||
#if defined(__clang__) && defined(__GLIBCXX__)
|
||||
void operator delete[](void* p, std::size_t sz) noexcept;
|
||||
#endif
|
||||
|
||||
namespace async_simple::coro::detail {
|
||||
|
||||
struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) Aligned_block {
|
||||
unsigned char pad[__STDCPP_DEFAULT_NEW_ALIGNMENT__];
|
||||
};
|
||||
|
||||
template <class Alloc>
|
||||
using Rebind =
|
||||
typename std::allocator_traits<Alloc>::template rebind_alloc<Aligned_block>;
|
||||
|
||||
// clang-format off
|
||||
template <class Alloc>
|
||||
concept HasRealPointers = std::same_as<Alloc, void> ||
|
||||
std::is_pointer_v<typename std::allocator_traits<Alloc>::pointer>;
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* Implementation comes from [P2502R2]
|
||||
* (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2502r2.pdf)
|
||||
*
|
||||
* This class can be used as the base class of the coroutine promise_type class.
|
||||
* It supports both stateless and stateful allocators.
|
||||
* The proposed interface requires that, if an allocator is provided,
|
||||
* it is the second argument to the coroutine function, immediately
|
||||
* preceded by an argument of type std::allocator_arg_t. This approach
|
||||
* is necessary to distinguish the allocator desired to allocate the coroutine
|
||||
* state from allocators whose purpose is to be used in the body of the
|
||||
* coroutine function.
|
||||
*
|
||||
* For Exmaple:
|
||||
* Generator<int> stateless_void_example(std::allocator_arg_t,
|
||||
* std::allocator<std::byte>) {
|
||||
* co_yield 42;
|
||||
* }
|
||||
*
|
||||
* stateless_void_example(std::allocator_arg, std::allocator<float>{});
|
||||
*
|
||||
*/
|
||||
template <class Allocator>
|
||||
class PromiseAllocator {
|
||||
private:
|
||||
using Alloc = Rebind<Allocator>;
|
||||
|
||||
static void* Allocate(Alloc al, const std::size_t size) {
|
||||
if constexpr (std::default_initializable<Alloc> &&
|
||||
std::allocator_traits<Alloc>::is_always_equal::value) {
|
||||
// do not store stateless allocator
|
||||
const size_t count =
|
||||
(size + sizeof(Aligned_block) - 1) / sizeof(Aligned_block);
|
||||
return al.allocate(count);
|
||||
} else {
|
||||
// store stateful allocator
|
||||
static constexpr size_t Align =
|
||||
(::std::max)(alignof(Alloc), sizeof(Aligned_block));
|
||||
const size_t count =
|
||||
(size + sizeof(Alloc) + Align - 1) / sizeof(Aligned_block);
|
||||
void* const ptr = al.allocate(count);
|
||||
const auto al_address =
|
||||
(reinterpret_cast<uintptr_t>(ptr) + size + alignof(Alloc) - 1) &
|
||||
~(alignof(Alloc) - 1);
|
||||
::new (reinterpret_cast<void*>(al_address)) Alloc(::std::move(al));
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// clang-format off
|
||||
static void* operator new(
|
||||
const size_t size) requires std::default_initializable<Alloc> {
|
||||
// clang-format on
|
||||
return Allocate(Alloc{}, size);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
template <class Alloc2, class... Args>
|
||||
requires std::convertible_to<const Alloc2&, Allocator>
|
||||
static void* operator new(const size_t size, std::allocator_arg_t,
|
||||
const Alloc2& al, const Args&...) {
|
||||
// clang-format on
|
||||
return Allocate(static_cast<Alloc>(static_cast<Allocator>(al)), size);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
template <class This, class Alloc2, class... Args>
|
||||
requires std::convertible_to<const Alloc2&, Allocator>
|
||||
static void* operator new(const size_t size, const This&,
|
||||
std::allocator_arg_t, const Alloc2& al,
|
||||
const Args&...) {
|
||||
// clang-format on
|
||||
return Allocate(static_cast<Alloc>(static_cast<Allocator>(al)), size);
|
||||
}
|
||||
|
||||
static void operator delete(void* const ptr, size_t size) noexcept {
|
||||
if constexpr (std::default_initializable<Alloc> &&
|
||||
std::allocator_traits<Alloc>::is_always_equal::value) {
|
||||
// make stateless allocator
|
||||
Alloc al{};
|
||||
const size_t count =
|
||||
(size + sizeof(Aligned_block) - 1) / sizeof(Aligned_block);
|
||||
al.deallocate(static_cast<Aligned_block*>(ptr), count);
|
||||
} else {
|
||||
// retrieve stateful allocator
|
||||
const auto _Al_address =
|
||||
(reinterpret_cast<uintptr_t>(ptr) + size + alignof(Alloc) - 1) &
|
||||
~(alignof(Alloc) - 1);
|
||||
auto& stored_al = *reinterpret_cast<Alloc*>(_Al_address);
|
||||
Alloc al{::std::move(stored_al)};
|
||||
stored_al.~Alloc();
|
||||
|
||||
static constexpr size_t Align =
|
||||
(::std::max)(alignof(Alloc), sizeof(Aligned_block));
|
||||
const size_t _Count =
|
||||
(size + sizeof(Alloc) + Align - 1) / sizeof(Aligned_block);
|
||||
al.deallocate(static_cast<Aligned_block*>(ptr), _Count);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class PromiseAllocator<void> { // type-erased allocator
|
||||
private:
|
||||
using DeallocFn = void (*)(void*, size_t);
|
||||
|
||||
template <class ProtoAlloc>
|
||||
static void* Allocate(const ProtoAlloc& proto, std::size_t size) {
|
||||
using Alloc = Rebind<ProtoAlloc>;
|
||||
auto al = static_cast<Alloc>(proto);
|
||||
|
||||
if constexpr (std::default_initializable<Alloc> &&
|
||||
std::allocator_traits<Alloc>::is_always_equal::value) {
|
||||
// don't store stateless allocator
|
||||
const DeallocFn dealloc = [](void* const ptr, const size_t size) {
|
||||
Alloc al{};
|
||||
const size_t count =
|
||||
(size + sizeof(DeallocFn) + sizeof(Aligned_block) - 1) /
|
||||
sizeof(Aligned_block);
|
||||
al.deallocate(static_cast<Aligned_block*>(ptr), count);
|
||||
};
|
||||
|
||||
const size_t count =
|
||||
(size + sizeof(DeallocFn) + sizeof(Aligned_block) - 1) /
|
||||
sizeof(Aligned_block);
|
||||
void* const ptr = al.allocate(count);
|
||||
::memcpy(static_cast<char*>(ptr) + size, &dealloc, sizeof(dealloc));
|
||||
return ptr;
|
||||
} else {
|
||||
// store stateful allocator
|
||||
static constexpr size_t Align =
|
||||
(::std::max)(alignof(Alloc), sizeof(Aligned_block));
|
||||
|
||||
const DeallocFn dealloc = [](void* const ptr, size_t size) {
|
||||
size += sizeof(DeallocFn);
|
||||
const auto al_address = (reinterpret_cast<uintptr_t>(ptr) +
|
||||
size + alignof(Alloc) - 1) &
|
||||
~(alignof(Alloc) - 1);
|
||||
auto& stored_al = *reinterpret_cast<const Alloc*>(al_address);
|
||||
Alloc al{::std::move(stored_al)};
|
||||
stored_al.~Alloc();
|
||||
|
||||
const size_t count =
|
||||
(size + sizeof(al) + Align - 1) / sizeof(Aligned_block);
|
||||
al.deallocate(static_cast<Aligned_block*>(ptr), count);
|
||||
};
|
||||
|
||||
const size_t count =
|
||||
(size + sizeof(DeallocFn) + sizeof(al) + Align - 1) /
|
||||
sizeof(Aligned_block);
|
||||
void* const ptr = al.allocate(count);
|
||||
::memcpy(static_cast<char*>(ptr) + size, &dealloc, sizeof(dealloc));
|
||||
size += sizeof(DeallocFn);
|
||||
const auto al_address =
|
||||
(reinterpret_cast<uintptr_t>(ptr) + size + alignof(Alloc) - 1) &
|
||||
~(alignof(Alloc) - 1);
|
||||
::new (reinterpret_cast<void*>(al_address)) Alloc{::std::move(al)};
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static void* operator new(const std::size_t size) { // defailt: new/delete
|
||||
void* const ptr = ::operator new[](size + sizeof(DeallocFn));
|
||||
const DeallocFn dealloc = [](void* const ptr, const size_t size) {
|
||||
::operator delete[](ptr, size + sizeof(DeallocFn));
|
||||
};
|
||||
::memcpy(static_cast<char*>(ptr) + size, &dealloc, sizeof(DeallocFn));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <class Alloc, class... Args>
|
||||
static void* operator new(const std::size_t size, std::allocator_arg_t,
|
||||
const Alloc& al, const Args&...) {
|
||||
static_assert(HasRealPointers<Alloc>,
|
||||
"coroutine allocators must use true pointers");
|
||||
return Allocate(al, size);
|
||||
}
|
||||
|
||||
template <class This, class Alloc, class... Args>
|
||||
static void* operator new(const std::size_t size, const This&,
|
||||
std::allocator_arg_t, const Alloc& al,
|
||||
const Args&...) {
|
||||
static_assert(HasRealPointers<Alloc>,
|
||||
"coroutine allocators must be true pointers");
|
||||
return Allocate(al, size);
|
||||
}
|
||||
|
||||
static void operator delete(void* const ptr,
|
||||
std::size_t size) noexcept {
|
||||
DeallocFn dealloc;
|
||||
::memcpy(&dealloc, static_cast<const char*>(ptr) + size,
|
||||
sizeof(DeallocFn));
|
||||
return dealloc(ptr, size);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace async_simple::coro::detail
|
||||
|
||||
#endif
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef ASYNC_SIMPLE_CORO_SEMAHORE_H
|
||||
#define ASYNC_SIMPLE_CORO_SEMAHORE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/coro/ConditionVariable.h"
|
||||
#include "async_simple/coro/SpinLock.h"
|
||||
|
||||
namespace async_simple::coro {
|
||||
|
||||
// Analogous to `std::counting_semaphore`, but it's for `Lazy`.
|
||||
// A counting_semaphore contains an internal counter initialized by the
|
||||
// constructor. This counter is decremented by calls to acquire() and related
|
||||
// methods, and is incremented by calls to release(). When the counter is zero,
|
||||
// acquire() blocks until the counter is incremented, It will suspend the
|
||||
// current coroutine and switch to other coroutines to run. But try_acquire()
|
||||
// does not block;
|
||||
template <std::size_t LeastMaxValue = std::numeric_limits<std::uint32_t>::max()>
|
||||
class CountingSemaphore {
|
||||
public:
|
||||
static_assert(LeastMaxValue <= std::numeric_limits<std::uint32_t>::max());
|
||||
|
||||
explicit CountingSemaphore(std::size_t desired) : count_(desired) {}
|
||||
~CountingSemaphore() = default;
|
||||
CountingSemaphore(const CountingSemaphore&) = delete;
|
||||
CountingSemaphore& operator=(const CountingSemaphore&) = delete;
|
||||
|
||||
// returns the maximum possible value of the internal counter
|
||||
static constexpr size_t max() noexcept { return LeastMaxValue; }
|
||||
|
||||
// decrements the internal counter or blocks until it can
|
||||
// If the internal counter is 0, the current coroutine will be suspended
|
||||
Lazy<void> acquire() noexcept;
|
||||
// increments the internal counter and unblocks acquirers
|
||||
Lazy<void> release(std::size_t update = 1) noexcept;
|
||||
|
||||
// tries to decrement the internal counter without blocking
|
||||
Lazy<bool> try_acquire() noexcept;
|
||||
|
||||
// TODO: To implement
|
||||
// template <class Rep, class Period>
|
||||
// bool try_acquire_for(std::chrono::duration<Rep, Period> expires_in);
|
||||
// template <class Clock, class Duration>
|
||||
// bool try_acquire_until(std::chrono::time_point<Clock, Duration>
|
||||
// expires_at);
|
||||
|
||||
private:
|
||||
using MutexType = SpinLock;
|
||||
|
||||
MutexType lock_;
|
||||
ConditionVariable<MutexType> cv_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
using BinarySemaphore = CountingSemaphore<1>;
|
||||
|
||||
template <std::size_t LeastMaxValue>
|
||||
Lazy<void> CountingSemaphore<LeastMaxValue>::acquire() noexcept {
|
||||
auto lock = co_await lock_.coScopedLock();
|
||||
co_await cv_.wait(lock_, [this] { return count_ > 0; });
|
||||
--count_;
|
||||
}
|
||||
|
||||
template <std::size_t LeastMaxValue>
|
||||
Lazy<void> CountingSemaphore<LeastMaxValue>::release(
|
||||
std::size_t update) noexcept {
|
||||
// update should be less than LeastMaxValue and greater than 0
|
||||
assert(update <= LeastMaxValue && update != 0);
|
||||
auto lock = co_await lock_.coScopedLock();
|
||||
// internal counter should be less than LeastMaxValue
|
||||
assert(count_ <= LeastMaxValue - update);
|
||||
count_ += update;
|
||||
if (update > 1) {
|
||||
// When update is greater than 1, wake up all coroutines.
|
||||
// When the counter is decremented to 0, resuspend the coroutine.
|
||||
cv_.notifyAll();
|
||||
} else {
|
||||
cv_.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
template <std::size_t LeastMaxValue>
|
||||
Lazy<bool> CountingSemaphore<LeastMaxValue>::try_acquire() noexcept {
|
||||
auto lock = co_await lock_.coScopedLock();
|
||||
if (count_) {
|
||||
--count_;
|
||||
co_return true;
|
||||
}
|
||||
co_return false;
|
||||
}
|
||||
|
||||
} // namespace async_simple::coro
|
||||
|
||||
#endif // ASYNC_SIMPLE_CORO_SEMAHORE_H
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
|
||||
#include "async_simple/coro/ConditionVariable.h"
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
#include "async_simple/coro/Mutex.h"
|
||||
#include "async_simple/coro/SpinLock.h"
|
||||
namespace async_simple::coro {
|
||||
|
||||
template <typename Lock>
|
||||
class SharedMutexBase {
|
||||
// Based on Howard Hinnant's reference implementation from N2406.
|
||||
|
||||
// The high bit of state_ is the write-entered flag which is set to
|
||||
// indicate a writer has taken the lock or is queuing to take the lock.
|
||||
// The remaining bits are the count of reader locks.
|
||||
//
|
||||
// To take a reader lock, block on gate1_ while the write-entered flag is
|
||||
// set or the maximum number of reader locks is held, then increment the
|
||||
// reader lock count.
|
||||
// To release, decrement the count, then if the write-entered flag is set
|
||||
// and the count is zero then signal gate2_ to wake a queued writer,
|
||||
// otherwise if the maximum number of reader locks was held signal gate1_
|
||||
// to wake a reader.
|
||||
//
|
||||
// To take a writer lock, block on gate1_ while the write-entered flag is
|
||||
// set, then set the write-entered flag to start queueing, then block on
|
||||
// gate2_ while the number of reader locks is non-zero.
|
||||
// To release, unset the write-entered flag and signal gate1_ to wake all
|
||||
// blocked readers and writers.
|
||||
//
|
||||
// This means that when no reader locks are held readers and writers get
|
||||
// equal priority. When one or more reader locks is held a writer gets
|
||||
// priority and no more reader locks can be taken while the writer is
|
||||
// queued.
|
||||
|
||||
// Only locked when accessing state_ or waiting on condition variables.
|
||||
Lock mut_;
|
||||
// Used to block while write-entered is set or reader count at maximum.
|
||||
ConditionVariable<Lock> gate1_;
|
||||
// Used to block queued writers while reader count is non-zero.
|
||||
ConditionVariable<Lock> gate2_;
|
||||
// The write-entered flag and reader count.
|
||||
unsigned state_;
|
||||
|
||||
static constexpr unsigned write_entered_flag =
|
||||
1U << (sizeof(unsigned) * CHAR_BIT - 1);
|
||||
static constexpr unsigned max_readers = ~write_entered_flag;
|
||||
|
||||
// Test whether the write-entered flag is set. mut_ must be locked.
|
||||
bool write_entered() const { return state_ & write_entered_flag; }
|
||||
|
||||
// The number of reader locks currently held. mut_ must be locked.
|
||||
unsigned readers() const { return state_ & max_readers; }
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
SharedMutexBase(Args&&... args)
|
||||
: mut_(std::forward<Args>(args)...), state_(0) {}
|
||||
|
||||
~SharedMutexBase() { assert(state_ == 0); }
|
||||
|
||||
SharedMutexBase(const SharedMutexBase&) = delete;
|
||||
SharedMutexBase& operator=(const SharedMutexBase&) = delete;
|
||||
|
||||
// Exclusive ownership
|
||||
|
||||
async_simple::coro::Lazy<> coLock() noexcept {
|
||||
auto scoper = co_await mut_.coScopedLock();
|
||||
// Wait until we can set the write-entered flag.
|
||||
|
||||
co_await gate1_.wait(mut_, [this] { return !write_entered(); });
|
||||
state_ |= write_entered_flag;
|
||||
// Then wait until there are no more readers.
|
||||
co_await gate2_.wait(mut_, [this] { return readers() == 0; });
|
||||
}
|
||||
|
||||
bool tryLock() noexcept {
|
||||
if (!mut_.tryLock()) {
|
||||
return false;
|
||||
}
|
||||
bool allow_write = (state_ == 0);
|
||||
if (allow_write) {
|
||||
state_ = write_entered_flag;
|
||||
}
|
||||
mut_.unlock();
|
||||
return allow_write;
|
||||
}
|
||||
|
||||
async_simple::coro::Lazy<void> unlock() noexcept {
|
||||
auto scoper = co_await mut_.coScopedLock();
|
||||
assert(write_entered());
|
||||
state_ = 0;
|
||||
// call notify_all() while mutex is held so that another thread can't
|
||||
// lock and unlock the mutex then destroy *this before we make the call.
|
||||
gate1_.notifyAll();
|
||||
}
|
||||
|
||||
// Shared ownership
|
||||
|
||||
async_simple::coro::Lazy<void> coLockShared() {
|
||||
auto scoper = co_await mut_.coScopedLock();
|
||||
co_await gate1_.wait(mut_, [this] { return state_ < max_readers; });
|
||||
++state_;
|
||||
}
|
||||
|
||||
bool tryLockShared() {
|
||||
if (!mut_.tryLock()) {
|
||||
return false;
|
||||
}
|
||||
bool allow_read = (state_ < max_readers);
|
||||
if (allow_read) {
|
||||
++state_;
|
||||
}
|
||||
mut_.unlock();
|
||||
return allow_read;
|
||||
}
|
||||
|
||||
async_simple::coro::Lazy<void> unlockShared() {
|
||||
auto scoper = co_await mut_.coScopedLock();
|
||||
assert(readers() > 0);
|
||||
auto prev = state_--;
|
||||
if (write_entered()) {
|
||||
// Wake the queued writer if there are no more readers.
|
||||
if (readers() == 0)
|
||||
gate2_.notifyOne();
|
||||
// No need to notify gate1_ because we give priority to the queued
|
||||
// writer, and that writer will eventually notify gate1_ after it
|
||||
// clears the write-entered flag.
|
||||
} else {
|
||||
// Wake any thread that was blocked on reader overflow.
|
||||
if (prev == max_readers)
|
||||
gate1_.notifyOne();
|
||||
}
|
||||
}
|
||||
};
|
||||
struct SharedMutex : public SharedMutexBase<SpinLock> {
|
||||
SharedMutex(int count = 128) : SharedMutexBase<SpinLock>(count) {}
|
||||
};
|
||||
} // namespace async_simple::coro
|
|
@ -16,8 +16,8 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_SPIN_LOCK_H
|
||||
#define ASYNC_SIMPLE_CORO_SPIN_LOCK_H
|
||||
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <thread>
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
|
|
@ -16,12 +16,12 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_SYNC_AWAIT_H
|
||||
#define ASYNC_SIMPLE_CORO_SYNC_AWAIT_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/Try.h>
|
||||
#include <async_simple/experimental/coroutine.h>
|
||||
#include <async_simple/util/Condition.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/Try.h"
|
||||
#include "async_simple/experimental/coroutine.h"
|
||||
#include "async_simple/util/Condition.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_TRAITS_H
|
||||
#define ASYNC_SIMPLE_CORO_TRAITS_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
#include "async_simple/Common.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
@ -26,48 +26,44 @@ namespace coro {
|
|||
|
||||
namespace detail {
|
||||
|
||||
template <class, class = void>
|
||||
struct HasCoAwaitMethod : std::false_type {};
|
||||
template <class T>
|
||||
concept HasCoAwaitMethod = requires(T&& awaitable) {
|
||||
std::forward<T>(awaitable).coAwait(nullptr);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct HasCoAwaitMethod<
|
||||
T, std::void_t<decltype(std::declval<T>().coAwait(nullptr))>>
|
||||
: std::true_type {};
|
||||
concept HasMemberCoAwaitOperator = requires(T&& awaitable) {
|
||||
std::forward<T>(awaitable).operator co_await();
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// FIXME: MSVC compiler bug, See
|
||||
// https://developercommunity.visualstudio.com/t/10160851
|
||||
template <class, class = void>
|
||||
struct HasMemberCoAwaitOperator : std::false_type {};
|
||||
struct HasGlobalCoAwaitOperatorHelp : std::false_type {};
|
||||
|
||||
template <class T>
|
||||
struct HasMemberCoAwaitOperator<
|
||||
T, std::void_t<decltype(std::declval<T>().operator co_await())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <class, class = void>
|
||||
struct HasGlobalCoAwaitOperator : std::false_type {};
|
||||
|
||||
template <class T>
|
||||
struct HasGlobalCoAwaitOperator<
|
||||
struct HasGlobalCoAwaitOperatorHelp<
|
||||
T, std::void_t<decltype(operator co_await(std::declval<T>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename Awaitable,
|
||||
std::enable_if_t<HasMemberCoAwaitOperator<Awaitable>::value, int> = 0>
|
||||
auto getAwaiter(Awaitable&& awaitable) {
|
||||
return std::forward<Awaitable>(awaitable).operator co_await();
|
||||
}
|
||||
template <class T>
|
||||
concept HasGlobalCoAwaitOperator = HasGlobalCoAwaitOperatorHelp<T>::value;
|
||||
#else
|
||||
template <class T>
|
||||
concept HasGlobalCoAwaitOperator = requires(T&& awaitable) {
|
||||
operator co_await(std::forward<T>(awaitable));
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename Awaitable,
|
||||
std::enable_if_t<HasGlobalCoAwaitOperator<Awaitable>::value, int> = 0>
|
||||
template <typename Awaitable>
|
||||
auto getAwaiter(Awaitable&& awaitable) {
|
||||
return operator co_await(std::forward<Awaitable>(awaitable));
|
||||
}
|
||||
|
||||
template <
|
||||
typename Awaitable,
|
||||
std::enable_if_t<!HasMemberCoAwaitOperator<Awaitable>::value, int> = 0,
|
||||
std::enable_if_t<!HasGlobalCoAwaitOperator<Awaitable>::value, int> = 0>
|
||||
auto getAwaiter(Awaitable&& awaitable) {
|
||||
return std::forward<Awaitable>(awaitable);
|
||||
if constexpr (HasMemberCoAwaitOperator<Awaitable>)
|
||||
return std::forward<Awaitable>(awaitable).operator co_await();
|
||||
else if constexpr (HasGlobalCoAwaitOperator<Awaitable>)
|
||||
return operator co_await(std::forward<Awaitable>(awaitable));
|
||||
else
|
||||
return std::forward<Awaitable>(awaitable);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
|
|
@ -25,10 +25,10 @@
|
|||
#ifndef ASYNC_SIMPLE_CORO_VIA_COROUTINE_H
|
||||
#define ASYNC_SIMPLE_CORO_VIA_COROUTINE_H
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/coro/Traits.h>
|
||||
#include <exception>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/coro/Traits.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
@ -175,20 +175,16 @@ struct [[nodiscard]] ViaAsyncAwaiter {
|
|||
// FIXME: In case awaitable is not a real awaitable, consider return
|
||||
// ReadyAwaiter instead. It would be much cheaper in case we `co_await
|
||||
// normal_function()`;
|
||||
template <
|
||||
typename Awaitable,
|
||||
std::enable_if_t<!detail::HasCoAwaitMethod<Awaitable>::value, int> = 0>
|
||||
template <typename Awaitable>
|
||||
inline auto coAwait(Executor* ex, Awaitable&& awaitable) {
|
||||
using AwaiterType =
|
||||
decltype(detail::getAwaiter(std::forward<Awaitable>(awaitable)));
|
||||
return ViaAsyncAwaiter<std::decay_t<AwaiterType>>(
|
||||
ex, std::forward<Awaitable>(awaitable));
|
||||
}
|
||||
|
||||
template <typename Awaitable,
|
||||
std::enable_if_t<detail::HasCoAwaitMethod<Awaitable>::value, int> = 0>
|
||||
inline auto coAwait(Executor* ex, Awaitable&& awaitable) {
|
||||
return std::forward<Awaitable>(awaitable).coAwait(ex);
|
||||
if constexpr (detail::HasCoAwaitMethod<Awaitable>) {
|
||||
return std::forward<Awaitable>(awaitable).coAwait(ex);
|
||||
} else {
|
||||
using AwaiterType =
|
||||
decltype(detail::getAwaiter(std::forward<Awaitable>(awaitable)));
|
||||
return ViaAsyncAwaiter<std::decay_t<AwaiterType>>(
|
||||
ex, std::forward<Awaitable>(awaitable));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
file(GLOB coro_test_src "*.cpp")
|
||||
add_executable(async_simple_coro_test ${coro_test_src} ${PROJECT_SOURCE_DIR}/async_simple/test/dotest.cpp)
|
||||
|
||||
target_link_libraries(async_simple_coro_test async_simple ${deplibs} ${testdeplibs})
|
||||
|
||||
add_test(NAME run_async_simple_coro_test COMMAND async_simple_coro_test)
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/ConditionVariable.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/coro/SpinLock.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
||||
#define CHECK_EXECUTOR(ex) \
|
||||
do { \
|
||||
EXPECT_TRUE((ex)->currentThreadInExecutor()) << (ex)->name(); \
|
||||
auto current = co_await CurrentExecutor(); \
|
||||
EXPECT_EQ((ex), current) << (ex)->name(); \
|
||||
} while (0)
|
||||
|
||||
class ConditionVariableTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
ConditionVariableTest() : _executor(2) {}
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
executors::SimpleExecutor _executor;
|
||||
};
|
||||
|
||||
TEST_F(ConditionVariableTest, testSingleWait) {
|
||||
auto data = 0;
|
||||
Notifier notifier;
|
||||
std::atomic<size_t> latch(2);
|
||||
|
||||
executors::SimpleExecutor e1(1);
|
||||
executors::SimpleExecutor e2(1);
|
||||
|
||||
auto producer = [&]() -> Lazy<void> {
|
||||
data = 1;
|
||||
CHECK_EXECUTOR(&e2);
|
||||
notifier.notify();
|
||||
CHECK_EXECUTOR(&e2);
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
auto awaiter = [&]() -> Lazy<void> {
|
||||
CHECK_EXECUTOR(&e1);
|
||||
co_await notifier.wait();
|
||||
CHECK_EXECUTOR(&e1);
|
||||
EXPECT_EQ(1, data);
|
||||
data = 2;
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
awaiter().via(&e1).start([](Try<void> var) {});
|
||||
producer().via(&e2).start([](Try<void> var) {});
|
||||
while (latch.load(std::memory_order_relaxed))
|
||||
;
|
||||
EXPECT_EQ(2, data);
|
||||
}
|
||||
|
||||
TEST_F(ConditionVariableTest, testMultiWait) {
|
||||
auto data = 0;
|
||||
Notifier notifier;
|
||||
|
||||
std::atomic<int> barrier(0);
|
||||
auto producer = [&]() -> Lazy<void> {
|
||||
std::this_thread::sleep_for(100us);
|
||||
data = 1;
|
||||
notifier.notify();
|
||||
co_return;
|
||||
};
|
||||
auto awaiter1 = [&]() -> Lazy<void> {
|
||||
co_await notifier.wait();
|
||||
EXPECT_EQ(1, data);
|
||||
barrier.fetch_add(1, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
auto awaiter2 = [&]() -> Lazy<void> {
|
||||
co_await notifier.wait();
|
||||
EXPECT_EQ(1, data);
|
||||
barrier.fetch_add(1, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
awaiter2().via(&_executor).start([](Try<void> var) {});
|
||||
awaiter1().via(&_executor).start([](Try<void> var) {});
|
||||
producer().via(&_executor).start([](Try<void> var) {});
|
||||
// spin wait
|
||||
while (barrier.load(std::memory_order_relaxed) < 2)
|
||||
;
|
||||
|
||||
notifier.reset();
|
||||
std::atomic<size_t> latch(2);
|
||||
auto producer2 = [&]() -> Lazy<void> {
|
||||
data = 0;
|
||||
notifier.notify();
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
auto awaiter3 = [&]() -> Lazy<void> {
|
||||
co_await notifier.wait();
|
||||
EXPECT_EQ(0, data);
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
producer2().via(&_executor).start([](Try<void> var) {});
|
||||
awaiter3().via(&_executor).start([](Try<void> var) {});
|
||||
while (latch.load(std::memory_order_relaxed))
|
||||
;
|
||||
EXPECT_EQ(0, data);
|
||||
}
|
||||
|
||||
TEST_F(ConditionVariableTest, testSingleWaitPredicate) {
|
||||
SpinLock mutex;
|
||||
ConditionVariable<SpinLock> cv;
|
||||
auto var = 0;
|
||||
|
||||
executors::SimpleExecutor e1(1);
|
||||
executors::SimpleExecutor e2(1);
|
||||
std::atomic<size_t> latch(2);
|
||||
|
||||
auto producer = [&]() -> Lazy<void> {
|
||||
CHECK_EXECUTOR(&e2);
|
||||
co_await mutex.coLock();
|
||||
CHECK_EXECUTOR(&e2);
|
||||
var = 1;
|
||||
cv.notify();
|
||||
CHECK_EXECUTOR(&e2);
|
||||
std::this_thread::sleep_for(500us);
|
||||
mutex.unlock();
|
||||
CHECK_EXECUTOR(&e2);
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
auto awaiter = [&]() -> Lazy<void> {
|
||||
CHECK_EXECUTOR(&e1);
|
||||
co_await mutex.coLock();
|
||||
CHECK_EXECUTOR(&e1);
|
||||
co_await cv.wait(mutex, [&] { return var > 0; });
|
||||
CHECK_EXECUTOR(&e1);
|
||||
mutex.unlock();
|
||||
CHECK_EXECUTOR(&e1);
|
||||
EXPECT_EQ(1, var);
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
co_return;
|
||||
};
|
||||
awaiter().via(&e1).start([](Try<void> var) {});
|
||||
producer().via(&e2).start([](Try<void> var) {});
|
||||
while (latch.load(std::memory_order_relaxed))
|
||||
;
|
||||
}
|
||||
|
||||
TEST_F(ConditionVariableTest, testSingleWaitPredicateWithScopeLock) {
|
||||
SpinLock mutex;
|
||||
ConditionVariable<SpinLock> cv;
|
||||
auto var = 0;
|
||||
|
||||
std::atomic<size_t> latch(2);
|
||||
auto producer = [&]() -> Lazy<void> {
|
||||
auto scoper = co_await mutex.coScopedLock();
|
||||
var = 1;
|
||||
cv.notify();
|
||||
co_return;
|
||||
};
|
||||
auto awaiter = [&]() -> Lazy<void> {
|
||||
auto scoper = co_await mutex.coScopedLock();
|
||||
co_await cv.wait(mutex, [&] { return var > 0; });
|
||||
EXPECT_EQ(1, var);
|
||||
co_return;
|
||||
};
|
||||
awaiter().via(&_executor).start([&](Try<void> var) {
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
});
|
||||
producer().via(&_executor).start([&](Try<void> var) {
|
||||
latch.fetch_sub(1u, std::memory_order_relaxed);
|
||||
});
|
||||
while (latch.load(std::memory_order_relaxed))
|
||||
;
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/FutureAwaiter.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/coro/SyncAwait.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
||||
class FutureAwaiterTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
FutureAwaiterTest() = default;
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
template <typename Callback>
|
||||
void sum(int a, int b, Callback&& callback) const {
|
||||
std::thread([callback = std::move(callback), a, b]() mutable {
|
||||
callback(a + b);
|
||||
}).detach();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FutureAwaiterTest, testWithFuture) {
|
||||
auto lazy1 = [&]() -> Lazy<> {
|
||||
Promise<int> pr;
|
||||
auto fut = pr.getFuture();
|
||||
sum(1, 1, [pr = std::move(pr)](int val) mutable { pr.setValue(val); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
auto val = co_await fut;
|
||||
EXPECT_EQ(2, val);
|
||||
};
|
||||
syncAwait(lazy1());
|
||||
auto lazy2 = [&]() -> Lazy<> {
|
||||
Promise<int> pr;
|
||||
auto fut = pr.getFuture();
|
||||
sum(1, 1, [pr = std::move(pr)](int val) mutable {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
pr.setValue(val);
|
||||
});
|
||||
auto val = co_await std::move(fut);
|
||||
EXPECT_EQ(2, val);
|
||||
};
|
||||
syncAwait(lazy2());
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
File diff suppressed because it is too large
Load Diff
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
#include "async_simple/coro/Mutex.h"
|
||||
#include "async_simple/coro/Sleep.h"
|
||||
#include "async_simple/executors/SimpleExecutor.h"
|
||||
|
||||
#include "async_simple/test/unittest.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
||||
class MutexTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
MutexTest() : _executor(4) {}
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
executors::SimpleExecutor _executor;
|
||||
};
|
||||
|
||||
TEST_F(MutexTest, testLock) {
|
||||
Mutex m;
|
||||
EXPECT_TRUE(m.tryLock());
|
||||
EXPECT_FALSE(m.tryLock());
|
||||
m.unlock();
|
||||
EXPECT_TRUE(m.tryLock());
|
||||
}
|
||||
|
||||
TEST_F(MutexTest, testAsyncLock) {
|
||||
Mutex m;
|
||||
int value = 0;
|
||||
std::atomic<int> count = 2;
|
||||
|
||||
auto writer = [&]() -> Lazy<void> {
|
||||
co_await m.coLock();
|
||||
value++;
|
||||
co_await async_simple::coro::sleep(1s);
|
||||
EXPECT_EQ(1, value);
|
||||
value--;
|
||||
m.unlock();
|
||||
count--;
|
||||
};
|
||||
|
||||
writer().via(&_executor).detach();
|
||||
writer().via(&_executor).detach();
|
||||
while (count) {
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, value);
|
||||
count = 2;
|
||||
|
||||
auto writer2 = [&]() -> Lazy<void> {
|
||||
auto scopedLock = co_await m.coScopedLock();
|
||||
value++;
|
||||
co_await async_simple::coro::sleep(1s);
|
||||
EXPECT_EQ(1, value);
|
||||
value--;
|
||||
count--;
|
||||
};
|
||||
|
||||
writer2().via(&_executor).detach();
|
||||
writer2().via(&_executor).detach();
|
||||
while (count) {
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, value);
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/coro/Sleep.h>
|
||||
#include <async_simple/coro/SyncAwait.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
namespace coro {
|
||||
|
||||
class SleepTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
TEST_F(SleepTest, testSleep) {
|
||||
executors::SimpleExecutor e1(5);
|
||||
|
||||
auto sleepTask = [&]() -> Lazy<> {
|
||||
auto current = co_await CurrentExecutor();
|
||||
EXPECT_EQ(&e1, current);
|
||||
|
||||
auto startTime = std::chrono::system_clock::now();
|
||||
co_await coro::sleep(1s);
|
||||
auto endTime = std::chrono::system_clock::now();
|
||||
|
||||
current = co_await CurrentExecutor();
|
||||
EXPECT_EQ(&e1, current);
|
||||
|
||||
auto duration = endTime - startTime;
|
||||
cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
||||
.count()
|
||||
<< endl;
|
||||
};
|
||||
|
||||
syncAwait(sleepTask().via(&e1));
|
||||
|
||||
auto sleepTask2 = [&]() -> Lazy<> {
|
||||
auto current = co_await CurrentExecutor();
|
||||
EXPECT_EQ(&e1, current);
|
||||
|
||||
auto startTime = std::chrono::system_clock::now();
|
||||
co_await coro::sleep(900ms);
|
||||
auto endTime = std::chrono::system_clock::now();
|
||||
|
||||
current = co_await CurrentExecutor();
|
||||
EXPECT_EQ(&e1, current);
|
||||
|
||||
auto duration = endTime - startTime;
|
||||
cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
||||
.count()
|
||||
<< endl;
|
||||
};
|
||||
|
||||
syncAwait(sleepTask2().via(&e1));
|
||||
|
||||
auto sleepTask3 = [&]() -> Lazy<> {
|
||||
class Executor : public async_simple::Executor {
|
||||
public:
|
||||
Executor() = default;
|
||||
virtual bool schedule(Func) override { return true; }
|
||||
virtual void schedule(Func func, Duration dur) override {
|
||||
std::thread([this, func = std::move(func), dur]() {
|
||||
id = std::this_thread::get_id();
|
||||
std::this_thread::sleep_for(dur);
|
||||
func();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
std::thread::id id;
|
||||
};
|
||||
Executor ex;
|
||||
auto startTime = std::chrono::system_clock::now();
|
||||
co_await coro::sleep(&ex, 900ms);
|
||||
auto endTime = std::chrono::system_clock::now();
|
||||
|
||||
std::cout << std::this_thread::get_id() << std::endl;
|
||||
std::cout << ex.id << std::endl;
|
||||
|
||||
EXPECT_EQ(std::this_thread::get_id(), ex.id);
|
||||
|
||||
auto duration = endTime - startTime;
|
||||
cout << std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
||||
.count()
|
||||
<< endl;
|
||||
};
|
||||
|
||||
syncAwait(sleepTask3().via(&e1));
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/SpinLock.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
|
||||
namespace async_simple {
|
||||
namespace coro {
|
||||
|
||||
class SpinLockTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
SpinLockTest() : _executor(2) {}
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
executors::SimpleExecutor _executor;
|
||||
};
|
||||
|
||||
TEST_F(SpinLockTest, testLockImmediately) {
|
||||
std::atomic<int> latch(5);
|
||||
SpinLock spin;
|
||||
auto data = 0;
|
||||
auto addOne = [&]() -> Lazy<> {
|
||||
auto scopeLock = co_await spin.coScopedLock();
|
||||
data++;
|
||||
latch--;
|
||||
co_return;
|
||||
};
|
||||
addOne().via(&_executor).start([](Try<void> var) {});
|
||||
addOne().via(&_executor).start([](Try<void> var) {});
|
||||
addOne().via(&_executor).start([](Try<void> var) {});
|
||||
addOne().via(&_executor).start([](Try<void> var) {});
|
||||
addOne().via(&_executor).start([](Try<void> var) {});
|
||||
while (latch) {
|
||||
}
|
||||
EXPECT_EQ(5, data);
|
||||
}
|
||||
|
||||
TEST_F(SpinLockTest, testLockYield) {
|
||||
std::atomic<int> latch(5);
|
||||
executors::SimpleExecutor executor(1);
|
||||
SpinLock spin(128);
|
||||
auto data = 0;
|
||||
auto addOneForgetUnlock = [&]() -> Lazy<> {
|
||||
co_await spin.coLock();
|
||||
data++;
|
||||
latch--;
|
||||
co_return;
|
||||
};
|
||||
auto addOne = [&]() -> Lazy<> {
|
||||
auto scopeLock = co_await spin.coScopedLock();
|
||||
data++;
|
||||
latch--;
|
||||
co_return;
|
||||
};
|
||||
auto assertDataEqual = [&]() -> Lazy<> {
|
||||
EXPECT_EQ(1, data);
|
||||
spin.unlock();
|
||||
co_return;
|
||||
};
|
||||
addOneForgetUnlock().via(&executor).start([](auto) {});
|
||||
addOne().via(&executor).start([](auto) {});
|
||||
addOne().via(&executor).start([](auto) {});
|
||||
addOne().via(&executor).start([](auto) {});
|
||||
addOne().via(&executor).start([](auto) {});
|
||||
assertDataEqual().via(&executor).start([](auto) {});
|
||||
while (latch) {
|
||||
}
|
||||
EXPECT_EQ(5, data);
|
||||
}
|
||||
|
||||
TEST_F(SpinLockTest, testSyncLock) {
|
||||
SpinLock spin;
|
||||
auto data = 0;
|
||||
|
||||
std::function<void()> addOne = [&]() {
|
||||
spin.lock();
|
||||
data++;
|
||||
spin.unlock();
|
||||
};
|
||||
|
||||
std::function<void()> subOne = [&]() {
|
||||
ScopedSpinLock scope_lock(spin);
|
||||
data--;
|
||||
};
|
||||
|
||||
std::vector<std::thread> ts;
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
ts.push_back(std::thread(addOne));
|
||||
ts.push_back(std::thread(subOne));
|
||||
}
|
||||
for (auto& t : ts) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, data);
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef ASYNC_SIMPLE_TEST_TIME_H
|
||||
#define ASYNC_SIMPLE_TEST_TIME_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std::chrono;
|
||||
class ScopeRuntime {
|
||||
public:
|
||||
explicit ScopeRuntime(const std::string& msg, int loop)
|
||||
: _msg(msg), _loop(loop) {
|
||||
auto d = steady_clock::now().time_since_epoch();
|
||||
auto mic = duration_cast<nanoseconds>(d);
|
||||
_start_time = mic.count();
|
||||
}
|
||||
~ScopeRuntime() {
|
||||
auto d = steady_clock::now().time_since_epoch();
|
||||
auto mic = duration_cast<nanoseconds>(d);
|
||||
long time_ns = ((mic.count() - _start_time) / _loop);
|
||||
double time_us = time_ns / 1000.0;
|
||||
double time_ms = time_us / 1000.0;
|
||||
if (time_ms > 100) {
|
||||
std::cout << std::right << std::setw(30) << _msg.data() << ": "
|
||||
<< time_ms << " ms" << std::endl;
|
||||
} else if (time_us > 100) {
|
||||
std::cout << std::right << std::setw(30) << _msg.data() << ": "
|
||||
<< time_us << " us" << std::endl;
|
||||
} else {
|
||||
std::cout << std::right << std::setw(30) << _msg.data() << ": "
|
||||
<< time_ns << " ns" << std::endl;
|
||||
}
|
||||
}
|
||||
int64_t _start_time;
|
||||
std::string _msg;
|
||||
int _loop;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/Traits.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
class Executor;
|
||||
namespace coro {
|
||||
|
||||
class TraitsTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
class A {
|
||||
public:
|
||||
bool coAwait(Executor *ex) { return true; }
|
||||
};
|
||||
class B {
|
||||
public:
|
||||
int value = 0;
|
||||
};
|
||||
|
||||
struct SimpleAwaiter {
|
||||
string name;
|
||||
SimpleAwaiter(string n) : name(n) {}
|
||||
};
|
||||
|
||||
class C {
|
||||
public:
|
||||
auto operator co_await() { return SimpleAwaiter("C Member"); }
|
||||
};
|
||||
|
||||
auto operator co_await(class A) { return SimpleAwaiter("A Global"); }
|
||||
|
||||
TEST_F(TraitsTest, testHasCoAwaitMethod) {
|
||||
EXPECT_TRUE(detail::HasCoAwaitMethod<A>::value);
|
||||
EXPECT_FALSE(detail::HasCoAwaitMethod<B>::value);
|
||||
}
|
||||
|
||||
TEST_F(TraitsTest, testHasCoAwaitOperator) {
|
||||
EXPECT_TRUE(detail::HasCoAwaitMethod<A>::value);
|
||||
EXPECT_FALSE(detail::HasMemberCoAwaitOperator<A>::value);
|
||||
EXPECT_TRUE(detail::HasGlobalCoAwaitOperator<A>::value);
|
||||
A a;
|
||||
auto awaiterA = detail::getAwaiter(a);
|
||||
EXPECT_EQ(string("A Global"), awaiterA.name);
|
||||
|
||||
EXPECT_FALSE(detail::HasCoAwaitMethod<B>::value);
|
||||
EXPECT_FALSE(detail::HasMemberCoAwaitOperator<B>::value);
|
||||
EXPECT_FALSE(detail::HasGlobalCoAwaitOperator<B>::value);
|
||||
B b;
|
||||
b.value = 3;
|
||||
B awaiterB = detail::getAwaiter(b);
|
||||
EXPECT_EQ(3, awaiterB.value);
|
||||
|
||||
EXPECT_FALSE(detail::HasCoAwaitMethod<C>::value);
|
||||
EXPECT_TRUE(detail::HasMemberCoAwaitOperator<C>::value);
|
||||
EXPECT_FALSE(detail::HasGlobalCoAwaitOperator<C>::value);
|
||||
C c;
|
||||
auto awaiterC = detail::getAwaiter(c);
|
||||
EXPECT_EQ(string("C Member"), awaiterC.name);
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/coro/SyncAwait.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
namespace coro {
|
||||
|
||||
class ViaCoroutineTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
std::atomic<int> check{0};
|
||||
class SimpleExecutorTest : public executors::SimpleExecutor {
|
||||
public:
|
||||
SimpleExecutorTest(size_t tn) : SimpleExecutor(tn) {}
|
||||
Context checkout() override {
|
||||
check++;
|
||||
return SimpleExecutor::checkout();
|
||||
}
|
||||
bool checkin(Func func, Context ctx, ScheduleOptions opts) override {
|
||||
// -1 is invalid ctx for SimpleExecutor
|
||||
if (ctx == (void*)-1) {
|
||||
return false;
|
||||
}
|
||||
check--;
|
||||
return SimpleExecutor::checkin(func, ctx, opts);
|
||||
}
|
||||
};
|
||||
|
||||
class Awaiter {
|
||||
public:
|
||||
bool await_ready() noexcept { return false; }
|
||||
bool await_suspend(std::coroutine_handle<> continuation) noexcept {
|
||||
return false;
|
||||
}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
TEST_F(ViaCoroutineTest, SimplecheckoutEQcheckin) {
|
||||
SimpleExecutorTest e1(10);
|
||||
auto Task = [&]() -> Lazy<> { co_await Awaiter(); };
|
||||
syncAwait(Task().via(&e1));
|
||||
EXPECT_EQ(check.load(), 0);
|
||||
}
|
||||
|
||||
} // namespace coro
|
||||
} // namespace async_simple
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include "async_simple/executors/SimpleExecutor.h"
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include <async_simple/Executor.h>
|
||||
#include <async_simple/executors/SimpleIOExecutor.h>
|
||||
#include <async_simple/util/ThreadPool.h>
|
||||
#include "async_simple/Executor.h"
|
||||
#include "async_simple/executors/SimpleIOExecutor.h"
|
||||
#include "async_simple/util/ThreadPool.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#ifndef FUTURE_SIMPLE_IO_EXECUTOR_H
|
||||
#define FUTURE_SIMPLE_IO_EXECUTOR_H
|
||||
|
||||
#include <async_simple/IOExecutor.h>
|
||||
#include "async_simple/IOExecutor.h"
|
||||
#ifndef ASYNC_SIMPLE_HAS_NOT_AIO
|
||||
#include <libaio.h>
|
||||
#endif
|
||||
|
@ -99,8 +99,10 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
void submitIO(int fd, iocb_cmd cmd, void* buffer, size_t length,
|
||||
off_t offset, AIOCallback cbfn) override {
|
||||
void submitIO([[maybe_unused]] int fd, [[maybe_unused]] iocb_cmd cmd,
|
||||
[[maybe_unused]] void* buffer, [[maybe_unused]] size_t length,
|
||||
[[maybe_unused]] off_t offset,
|
||||
[[maybe_unused]] AIOCallback cbfn) override {
|
||||
#ifndef ASYNC_SIMPLE_HAS_NOT_AIO
|
||||
iocb io;
|
||||
memset(&io, 0, sizeof(iocb));
|
||||
|
@ -122,8 +124,10 @@ public:
|
|||
}
|
||||
#endif
|
||||
}
|
||||
void submitIOV(int fd, iocb_cmd cmd, const iovec_t* iov, size_t count,
|
||||
off_t offset, AIOCallback cbfn) override {
|
||||
void submitIOV([[maybe_unused]] int fd, [[maybe_unused]] iocb_cmd cmd,
|
||||
[[maybe_unused]] const iovec_t* iov,
|
||||
[[maybe_unused]] size_t count, [[maybe_unused]] off_t offset,
|
||||
[[maybe_unused]] AIOCallback cbfn) override {
|
||||
#ifndef ASYNC_SIMPLE_HAS_NOT_AIO
|
||||
iocb io;
|
||||
memset(&io, 0, sizeof(iocb));
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
file(GLOB executor_test_src "*.cpp")
|
||||
add_executable(async_simple_executor_test ${executor_test_src} ${PROJECT_SOURCE_DIR}/async_simple/test/dotest.cpp)
|
||||
|
||||
target_link_libraries(async_simple_executor_test async_simple ${deplibs} ${testdeplibs})
|
||||
|
||||
add_test(NAME run_async_simple_executor_test COMMAND async_simple_executor_test)
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef ASYNC_SIMPLE_HAS_NOT_AIO
|
||||
#include <fcntl.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <malloc.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
|
||||
#include "async_simple/executors/SimpleIOExecutor.h"
|
||||
#include "async_simple/test/unittest.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
using namespace async_simple::executors;
|
||||
|
||||
class SimpleIOExecutorTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
static constexpr int32_t kBufferSize = 4096 * 2;
|
||||
static constexpr auto kTestFile = "/tmp/async_simple_io_test.tmp";
|
||||
|
||||
public:
|
||||
void caseSetUp() override {
|
||||
_ioExecutor = std::make_shared<SimpleIOExecutor>();
|
||||
ASSERT_TRUE(_ioExecutor->init());
|
||||
_executor = _ioExecutor.get();
|
||||
}
|
||||
|
||||
void caseTearDown() override { _ioExecutor->destroy(); }
|
||||
|
||||
public:
|
||||
std::shared_ptr<SimpleIOExecutor> _ioExecutor;
|
||||
IOExecutor* _executor;
|
||||
};
|
||||
|
||||
TEST_F(SimpleIOExecutorTest, testNormal) {
|
||||
std::string expect(4096, '0');
|
||||
auto fd = open(kTestFile, O_RDWR | O_DIRECT | O_CREAT, 0600);
|
||||
auto output = memalign(4096, kBufferSize);
|
||||
memcpy((char*)output, expect.data(), expect.length());
|
||||
_executor->submitIO(
|
||||
fd, IOCB_CMD_PWRITE, output, expect.length(), 0,
|
||||
[](io_event_t& event) mutable { EXPECT_EQ(4096, (int32_t)event.res); });
|
||||
std::this_thread::sleep_for(300ms);
|
||||
memset(output, 0, kBufferSize);
|
||||
_executor->submitIO(fd, IOCB_CMD_PREAD, output, kBufferSize, 0,
|
||||
[&expect, output](io_event_t event) mutable {
|
||||
EXPECT_EQ(4096, (int32_t)event.res);
|
||||
EXPECT_EQ(expect,
|
||||
std::string((char*)output, event.res));
|
||||
});
|
||||
std::this_thread::sleep_for(300ms);
|
||||
close(fd);
|
||||
free(output);
|
||||
unlink(kTestFile);
|
||||
}
|
||||
|
||||
TEST_F(SimpleIOExecutorTest, testException) {
|
||||
auto output = memalign(4096, kBufferSize);
|
||||
memset(output, 0, kBufferSize);
|
||||
_executor->submitIO(
|
||||
-1, IOCB_CMD_PREAD, output, kBufferSize, 0,
|
||||
[](io_event_t& event) mutable { EXPECT_TRUE((int32_t)event.res < 0); });
|
||||
std::this_thread::sleep_for(300ms);
|
||||
free(output);
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
||||
#endif
|
|
@ -1,6 +0,0 @@
|
|||
file(GLOB test_src "*.cpp")
|
||||
add_executable(async_simple_test ${test_src})
|
||||
|
||||
target_link_libraries(async_simple_test async_simple ${deplibs} ${testdeplibs})
|
||||
|
||||
add_test(NAME run_async_simple_test COMMAND async_simple_test)
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/FutureState.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace async_simple::executors;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
class FutureStateTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
enum DummyState : int {
|
||||
CONSTRUCTED = 1,
|
||||
DESTRUCTED = 2,
|
||||
};
|
||||
|
||||
struct Dummy {
|
||||
Dummy() : state(nullptr) {}
|
||||
Dummy(int* state_) : state(state_) {
|
||||
if (state) {
|
||||
(*state) |= CONSTRUCTED;
|
||||
}
|
||||
}
|
||||
Dummy(Dummy&& other) : state(other.state) { other.state = nullptr; }
|
||||
Dummy& operator=(Dummy&& other) {
|
||||
if (this != &other) {
|
||||
std::swap(other.state, state);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Dummy() {
|
||||
if (state) {
|
||||
(*state) |= DESTRUCTED;
|
||||
state = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Dummy(const Dummy&) = delete;
|
||||
Dummy& operator=(const Dummy&) = delete;
|
||||
|
||||
int* state = nullptr;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(FutureStateTest, testSimpleProcess) {
|
||||
auto fs = new FutureState<int>();
|
||||
fs->attachOne();
|
||||
ASSERT_FALSE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
ASSERT_FALSE(fs->getExecutor());
|
||||
|
||||
fs->setResult(Try<int>(100));
|
||||
ASSERT_TRUE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
auto& v = fs->getTry();
|
||||
ASSERT_EQ(100, v.value());
|
||||
|
||||
int output = 0;
|
||||
fs->setContinuation(
|
||||
[&output](Try<int>&& v) mutable { output = v.value() + 5; });
|
||||
ASSERT_TRUE(fs->hasResult());
|
||||
ASSERT_TRUE(fs->hasContinuation());
|
||||
|
||||
ASSERT_EQ(105, output);
|
||||
|
||||
// you can not getTry after setContinuation,
|
||||
// because value has been moved into continuation
|
||||
// auto v = std::move(fs->getTry());
|
||||
// ASSERT_EQ(100, v.value());
|
||||
|
||||
fs->detachOne();
|
||||
}
|
||||
|
||||
TEST_F(FutureStateTest, testSimpleExecutor) {
|
||||
auto fs = new FutureState<int>();
|
||||
fs->attachOne();
|
||||
auto executor = new SimpleExecutor(5);
|
||||
fs->setExecutor(executor);
|
||||
ASSERT_FALSE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
ASSERT_TRUE(fs->getExecutor());
|
||||
|
||||
fs->setResult(Try<int>(100));
|
||||
ASSERT_TRUE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
|
||||
int output = 0;
|
||||
fs->setContinuation(
|
||||
[&output](Try<int>&& v) mutable { output = v.value() + 5; });
|
||||
ASSERT_TRUE(fs->hasResult());
|
||||
ASSERT_TRUE(fs->hasContinuation());
|
||||
|
||||
delete executor;
|
||||
ASSERT_EQ(105, output);
|
||||
|
||||
// auto v = std::move(fs->getTry());
|
||||
// ASSERT_EQ(100, v.value());
|
||||
fs->detachOne();
|
||||
}
|
||||
|
||||
TEST_F(FutureStateTest, testClass) {
|
||||
auto fs = new FutureState<Dummy>();
|
||||
fs->attachOne();
|
||||
auto executor = new SimpleExecutor(5);
|
||||
fs->setExecutor(executor);
|
||||
|
||||
ASSERT_FALSE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
EXPECT_TRUE(fs->getExecutor());
|
||||
|
||||
int state = 0;
|
||||
Try<Dummy> v(&state);
|
||||
fs->setResult(std::move(v));
|
||||
ASSERT_TRUE(fs->hasResult());
|
||||
ASSERT_FALSE(fs->hasContinuation());
|
||||
ASSERT_TRUE(fs->getTry().value().state);
|
||||
|
||||
int* output = nullptr;
|
||||
Dummy noCopyable(nullptr);
|
||||
fs->setContinuation(
|
||||
[&output, d = std::move(noCopyable)](Try<Dummy>&& v) mutable {
|
||||
auto localV = std::move(v);
|
||||
output = localV.value().state;
|
||||
(void)d;
|
||||
});
|
||||
EXPECT_TRUE(fs->hasResult());
|
||||
EXPECT_TRUE(fs->hasContinuation());
|
||||
|
||||
delete executor;
|
||||
EXPECT_EQ(&state, output);
|
||||
|
||||
EXPECT_FALSE(fs->getTry().value().state);
|
||||
fs->detachOne();
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
|
@ -1,492 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <exception>
|
||||
|
||||
#include <async_simple/Collect.h>
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace async_simple::executors;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
class FutureTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
template <typename T>
|
||||
void doTestType(bool readyFuture);
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
enum DummyState : int {
|
||||
CONSTRUCTED = 1,
|
||||
DESTRUCTED = 2,
|
||||
};
|
||||
|
||||
struct Dummy {
|
||||
Dummy() : state(nullptr), value(0) {}
|
||||
Dummy(int x) : state(nullptr), value(x) {}
|
||||
Dummy(int* state_) : state(state_), value(0) {
|
||||
if (state) {
|
||||
(*state) |= CONSTRUCTED;
|
||||
}
|
||||
}
|
||||
Dummy(Dummy&& other) : state(other.state), value(other.value) {
|
||||
other.state = nullptr;
|
||||
}
|
||||
Dummy& operator=(Dummy&& other) {
|
||||
if (this != &other) {
|
||||
std::swap(other.state, state);
|
||||
std::swap(other.value, value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Dummy() {
|
||||
if (state) {
|
||||
(*state) |= DESTRUCTED;
|
||||
state = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Dummy(const Dummy&) = delete;
|
||||
Dummy& operator=(const Dummy&) = delete;
|
||||
|
||||
int* state = nullptr;
|
||||
int value = 0;
|
||||
|
||||
Dummy& operator+(const int& rhs) & {
|
||||
value += rhs;
|
||||
return *this;
|
||||
}
|
||||
Dummy&& operator+(const int& rhs) && {
|
||||
value += rhs;
|
||||
return std::move(*this);
|
||||
}
|
||||
Dummy& operator+(const Dummy& rhs) & {
|
||||
value += rhs.value;
|
||||
return *this;
|
||||
}
|
||||
Dummy&& operator+(const Dummy& rhs) && {
|
||||
value += rhs.value;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
bool operator==(const Dummy& other) const { return value == other.value; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(FutureTest, testSimpleProcess) {
|
||||
SimpleExecutor executor(5);
|
||||
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture();
|
||||
EXPECT_TRUE(p.valid());
|
||||
|
||||
int output = 0;
|
||||
auto f = std::move(future).via(&executor).thenTry([&output](Try<int>&& t) {
|
||||
output = t.value();
|
||||
return 123;
|
||||
});
|
||||
|
||||
p.setValue(456);
|
||||
|
||||
f.wait();
|
||||
const Try<int>& t = f.result();
|
||||
EXPECT_TRUE(t.available());
|
||||
EXPECT_FALSE(t.hasError());
|
||||
|
||||
EXPECT_EQ(123, std::move(f).get());
|
||||
EXPECT_EQ(456, output);
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testGetSet) {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture();
|
||||
ASSERT_THROW(p.getFuture(), std::logic_error);
|
||||
p.setValue(456);
|
||||
f.wait();
|
||||
EXPECT_EQ(456, f.value());
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testThenValue) {
|
||||
SimpleExecutor executor(5);
|
||||
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture();
|
||||
EXPECT_TRUE(p.valid());
|
||||
|
||||
int output = 0;
|
||||
auto f =
|
||||
std::move(future).via(&executor).thenValue([&output](const int64_t& t) {
|
||||
output = t;
|
||||
return 123;
|
||||
});
|
||||
|
||||
p.setValue(456);
|
||||
|
||||
f.wait();
|
||||
const Try<int>& t = f.result();
|
||||
EXPECT_TRUE(t.available());
|
||||
EXPECT_FALSE(t.hasError());
|
||||
|
||||
EXPECT_EQ(123, std::move(f).get());
|
||||
EXPECT_EQ(456, output);
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testChainedFuture) {
|
||||
SimpleExecutor executor(5);
|
||||
Promise<int> p;
|
||||
int output0 = 0;
|
||||
int output1 = 0;
|
||||
int output2 = 0;
|
||||
std::vector<int> order;
|
||||
std::mutex mtx;
|
||||
auto record = [&order, &mtx](int x) {
|
||||
std::lock_guard<std::mutex> l(mtx);
|
||||
order.push_back(x);
|
||||
};
|
||||
auto future = p.getFuture().via(&executor);
|
||||
auto f = std::move(future)
|
||||
.thenTry([&output0, record](Try<int>&& t) {
|
||||
record(0);
|
||||
output0 = t.value();
|
||||
return t.value() + 100;
|
||||
})
|
||||
.thenTry([&output1, &executor, record](Try<int>&& t) {
|
||||
record(1);
|
||||
output1 = t.value();
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(&executor);
|
||||
p.setValue(t.value() + 10);
|
||||
return f;
|
||||
})
|
||||
.thenValue([&output2, record](int x) {
|
||||
record(2);
|
||||
output2 = x;
|
||||
return std::to_string(x);
|
||||
})
|
||||
.thenValue([](string&& s) { return 1111.0; });
|
||||
p.setValue(1000);
|
||||
f.wait();
|
||||
EXPECT_EQ(3u, order.size());
|
||||
int last = -1;
|
||||
for (auto a : order) {
|
||||
EXPECT_LT(last, a);
|
||||
last = a;
|
||||
}
|
||||
EXPECT_EQ(1000, output0);
|
||||
EXPECT_EQ(1100, output1);
|
||||
EXPECT_EQ(1110, output2);
|
||||
EXPECT_EQ(1111.0, std::move(f).get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void FutureTest::doTestType(bool readyFuture) {
|
||||
SimpleExecutor executor(5);
|
||||
Promise<T> p;
|
||||
std::vector<int> order;
|
||||
std::mutex mtx;
|
||||
auto record = [&order, &mtx](int x) {
|
||||
std::lock_guard<std::mutex> l(mtx);
|
||||
order.push_back(x);
|
||||
};
|
||||
auto future = p.getFuture().via(&executor);
|
||||
if (readyFuture) {
|
||||
future = makeReadyFuture(T(1000));
|
||||
}
|
||||
auto f = std::move(future)
|
||||
.thenTry([record](Try<T>&& t) mutable {
|
||||
record(0);
|
||||
return std::move(t).value() + 100;
|
||||
})
|
||||
.thenTry([&executor, record](Try<T> t) mutable {
|
||||
record(1);
|
||||
Promise<T> p;
|
||||
auto f = p.getFuture().via(&executor);
|
||||
p.setValue(std::move(t).value() + 10);
|
||||
return f;
|
||||
})
|
||||
.thenValue([record](T&& x) mutable {
|
||||
record(2);
|
||||
return std::move(x) + 1;
|
||||
});
|
||||
p.setValue(T(1000));
|
||||
f.wait();
|
||||
EXPECT_EQ(3u, order.size());
|
||||
int last = -1;
|
||||
for (auto a : order) {
|
||||
EXPECT_LT(last, a);
|
||||
last = a;
|
||||
}
|
||||
EXPECT_EQ(T(1111), std::move(f).get());
|
||||
}
|
||||
TEST_F(FutureTest, testClass) {
|
||||
doTestType<int>(true);
|
||||
doTestType<Dummy>(true);
|
||||
doTestType<int>(false);
|
||||
doTestType<Dummy>(false);
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testException) {
|
||||
SimpleExecutor executor(5);
|
||||
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture().via(&executor);
|
||||
EXPECT_TRUE(p.valid());
|
||||
|
||||
auto f = std::move(future)
|
||||
.thenTry([](Try<int> x) { return x.value() + 100; })
|
||||
.thenValue([](int x) { return x + 10; })
|
||||
.thenTry([](Try<int> x) {
|
||||
try {
|
||||
return x.value() + 1.0;
|
||||
} catch (...) {
|
||||
return -1.0;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
throw std::runtime_error("FAILED");
|
||||
} catch (...) {
|
||||
p.setException(std::current_exception());
|
||||
}
|
||||
|
||||
f.wait();
|
||||
const Try<double>& t = f.result();
|
||||
EXPECT_TRUE(t.available());
|
||||
EXPECT_FALSE(t.hasError());
|
||||
EXPECT_EQ(-1.0, std::move(f).get());
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testVoid) {
|
||||
SimpleExecutor executor(5);
|
||||
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture().via(&executor);
|
||||
EXPECT_TRUE(p.valid());
|
||||
int output = 0;
|
||||
auto f = std::move(future)
|
||||
.thenTry([&output](Try<int> x) { output = x.value(); })
|
||||
.thenTry([](auto&&) { return 200; });
|
||||
|
||||
p.setValue(100);
|
||||
f.wait();
|
||||
EXPECT_EQ(200, std::move(f).get());
|
||||
EXPECT_EQ(100, output);
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testWait) {
|
||||
SimpleExecutor executor(5);
|
||||
int output;
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture().via(&executor);
|
||||
EXPECT_TRUE(p.valid());
|
||||
std::atomic<bool> beginCallback(false);
|
||||
std::atomic<int> doneCallback(0);
|
||||
auto f = std::move(future).thenTry(
|
||||
[&output, &beginCallback, &doneCallback](Try<int> x) {
|
||||
int tmp = doneCallback.load(std::memory_order_acquire);
|
||||
while (!doneCallback.compare_exchange_weak(tmp, 1)) {
|
||||
tmp = doneCallback.load(std::memory_order_acquire);
|
||||
}
|
||||
while (!beginCallback.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
output = x.value();
|
||||
return output + 5;
|
||||
});
|
||||
// use doneCallback to know which step future finish.
|
||||
// use beginCallback to notify future start callback function.
|
||||
auto t = std::thread([&p, &beginCallback, &doneCallback]() {
|
||||
std::this_thread::sleep_for(100000us);
|
||||
// sleep a little time and make sure the future callback do not start.
|
||||
EXPECT_EQ(0, doneCallback.load(std::memory_order_acquire));
|
||||
p.setValue(100);
|
||||
for (size_t i = 5;
|
||||
i > 0 && doneCallback.load(std::memory_order_acquire) != 1; i--) {
|
||||
std::this_thread::sleep_for(1000us);
|
||||
}
|
||||
// the future callback has started but can not finish.
|
||||
EXPECT_EQ(1, doneCallback.load(std::memory_order_acquire));
|
||||
// notify future finish callback
|
||||
beginCallback.store(true, std::memory_order_release);
|
||||
for (size_t i = 500;
|
||||
i > 0 && doneCallback.load(std::memory_order_acquire) != 2; i--) {
|
||||
std::this_thread::sleep_for(10000us);
|
||||
}
|
||||
// make sure future callback has finished
|
||||
EXPECT_EQ(2, doneCallback.load(std::memory_order_acquire));
|
||||
});
|
||||
f.wait();
|
||||
doneCallback.store(2, std::memory_order_release);
|
||||
EXPECT_EQ(105, std::move(f).get());
|
||||
EXPECT_EQ(100, output);
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testWaitCallback) {
|
||||
SimpleExecutor executor(2), executor2(1);
|
||||
Promise<int> p;
|
||||
auto future = p.getFuture().via(&executor);
|
||||
EXPECT_TRUE(p.valid());
|
||||
Promise<bool> p2;
|
||||
auto f =
|
||||
std::move(future)
|
||||
.thenTry([&p2, &executor2](Try<int> res) {
|
||||
auto f = p2.getFuture()
|
||||
.via(&executor2)
|
||||
.thenValue([x = std::move(res).value()](bool y) {
|
||||
std::this_thread::sleep_for(10000us);
|
||||
return x;
|
||||
});
|
||||
return f;
|
||||
})
|
||||
.thenValue([](int x) {
|
||||
std::this_thread::sleep_for(20000us);
|
||||
return std::make_pair(x + 1, x);
|
||||
})
|
||||
.thenValue([&executor2](std::pair<int, int>&& res) {
|
||||
// return res.first * res.second;
|
||||
Promise<bool> p3;
|
||||
Future<int> f = p3.getFuture()
|
||||
.via(&executor2)
|
||||
.thenValue([r = std::move(res)](bool y) {
|
||||
std::this_thread::sleep_for(30000us);
|
||||
return r.first * r.second;
|
||||
});
|
||||
p3.setValue(true);
|
||||
// return std::move(f).get();
|
||||
return f;
|
||||
});
|
||||
p.setValue(2);
|
||||
p2.setValue(true);
|
||||
f.wait();
|
||||
ASSERT_EQ(6, std::move(f).get());
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testCollectAll) {
|
||||
SimpleExecutor executor(15);
|
||||
|
||||
size_t n = 10;
|
||||
vector<Promise<Dummy>> promise(n);
|
||||
vector<Future<Dummy>> futures;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
futures.push_back(promise[i].getFuture().via(&executor));
|
||||
}
|
||||
vector<int> expected;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
expected.push_back(i);
|
||||
}
|
||||
auto f = collectAll(futures.begin(), futures.end())
|
||||
.thenValue([&expected](vector<Try<Dummy>>&& vec) {
|
||||
EXPECT_EQ(expected.size(), vec.size());
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
EXPECT_EQ(expected[i], vec[i].value().value);
|
||||
}
|
||||
expected.clear();
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
promise[i].setValue(Dummy(i));
|
||||
}
|
||||
|
||||
f.wait();
|
||||
|
||||
EXPECT_TRUE(expected.empty());
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testCollectReadyFutures) {
|
||||
SimpleExecutor executor(15);
|
||||
|
||||
size_t n = 10;
|
||||
vector<Future<Dummy>> futures;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
futures.push_back(makeReadyFuture<Dummy>(i));
|
||||
}
|
||||
bool executed = false;
|
||||
auto f = collectAll(futures.begin(), futures.end())
|
||||
.thenValue([&executed, n](vector<Try<Dummy>>&& vec) {
|
||||
EXPECT_EQ(n, vec.size());
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
EXPECT_EQ(
|
||||
i, static_cast<decltype(i)>(vec[i].value().value));
|
||||
}
|
||||
executed = true;
|
||||
});
|
||||
EXPECT_TRUE(f.TEST_hasLocalState());
|
||||
f.wait();
|
||||
EXPECT_TRUE(executed);
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testPromiseBroken) {
|
||||
Promise<Dummy> p;
|
||||
auto f = p.getFuture();
|
||||
{
|
||||
// destruct p
|
||||
auto innerP = std::move(p);
|
||||
(void)innerP;
|
||||
}
|
||||
f.wait();
|
||||
auto& r = f.result();
|
||||
EXPECT_TRUE(r.available());
|
||||
EXPECT_TRUE(r.hasError());
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testViaAfterWait) {
|
||||
Promise<int> promise;
|
||||
auto future = promise.getFuture();
|
||||
|
||||
auto t = std::thread([p = std::move(promise)]() mutable {
|
||||
std::this_thread::sleep_for(1s);
|
||||
p.setValue(100);
|
||||
});
|
||||
|
||||
future.wait();
|
||||
std::move(future).via(nullptr).thenValue(
|
||||
[](int v) mutable { ASSERT_EQ(100, v); });
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testReadyFuture) {
|
||||
auto future = makeReadyFuture(3);
|
||||
future.wait();
|
||||
std::move(future).via(nullptr).thenValue(
|
||||
[](int v) mutable { ASSERT_EQ(3, v); });
|
||||
}
|
||||
|
||||
TEST_F(FutureTest, testPromiseCopy) {
|
||||
auto promise1 = std::make_unique<Promise<int>>();
|
||||
auto promise2 = std::make_unique<Promise<int>>();
|
||||
promise2->setValue(0);
|
||||
auto future = promise1->getFuture();
|
||||
*promise1 = *promise2;
|
||||
promise1.reset();
|
||||
ASSERT_THROW(future.value(), std::runtime_error);
|
||||
auto promise3 = *promise2;
|
||||
promise2.reset();
|
||||
EXPECT_EQ(0, promise3.getFuture().value());
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <exception>
|
||||
|
||||
#include <async_simple/Try.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
class TryTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
enum DummyState : int {
|
||||
CONSTRUCTED = 1,
|
||||
DESTRUCTED = 2,
|
||||
};
|
||||
|
||||
struct Dummy {
|
||||
Dummy() = default;
|
||||
Dummy(int* state_) : state(state_) {
|
||||
if (state) {
|
||||
*state |= CONSTRUCTED;
|
||||
}
|
||||
}
|
||||
Dummy(Dummy&& other) : state(other.state) { other.state = nullptr; }
|
||||
Dummy& operator=(Dummy&& other) {
|
||||
std::swap(other.state, state);
|
||||
return *this;
|
||||
}
|
||||
~Dummy() {
|
||||
if (state) {
|
||||
*state |= DESTRUCTED;
|
||||
state = nullptr;
|
||||
}
|
||||
}
|
||||
int* state = nullptr;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(TryTest, testSimpleProcess) {
|
||||
Try<int> v0(1);
|
||||
ASSERT_EQ(1, v0.value());
|
||||
|
||||
Try<int> v1 = 1;
|
||||
ASSERT_EQ(1, v1.value());
|
||||
|
||||
Try<int> v2 = std::move(v0);
|
||||
ASSERT_EQ(1, v2.value());
|
||||
|
||||
Try<int> v3(std::move(v1));
|
||||
ASSERT_TRUE(v3.available());
|
||||
ASSERT_FALSE(v3.hasError());
|
||||
ASSERT_EQ(1, v3.value());
|
||||
|
||||
Try<int> v4;
|
||||
ASSERT_FALSE(v4.available());
|
||||
|
||||
bool hasException = false;
|
||||
Try<int> ve;
|
||||
try {
|
||||
throw "abcdefg";
|
||||
} catch (...) {
|
||||
ve = std::current_exception();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(ve.available());
|
||||
ASSERT_TRUE(ve.hasError());
|
||||
|
||||
try {
|
||||
ve.value();
|
||||
} catch (...) {
|
||||
hasException = true;
|
||||
}
|
||||
ASSERT_TRUE(hasException);
|
||||
|
||||
Try<int> emptyV;
|
||||
ASSERT_FALSE(emptyV.available());
|
||||
emptyV = 100;
|
||||
ASSERT_TRUE(emptyV.available());
|
||||
ASSERT_FALSE(emptyV.hasError());
|
||||
ASSERT_EQ(100, emptyV.value());
|
||||
}
|
||||
|
||||
TEST_F(TryTest, testClass) {
|
||||
int state0 = 0;
|
||||
Try<Dummy> v0{Dummy(&state0)};
|
||||
EXPECT_TRUE(v0.available());
|
||||
EXPECT_FALSE(v0.hasError());
|
||||
EXPECT_TRUE(state0 & CONSTRUCTED);
|
||||
EXPECT_FALSE(state0 & DESTRUCTED);
|
||||
std::exception_ptr error;
|
||||
v0 = error;
|
||||
EXPECT_TRUE(v0.hasError());
|
||||
EXPECT_TRUE(state0 & CONSTRUCTED);
|
||||
EXPECT_TRUE(state0 & DESTRUCTED);
|
||||
}
|
||||
|
||||
TEST_F(TryTest, testVoid) {
|
||||
Try<void> v;
|
||||
bool hasException = false;
|
||||
std::exception_ptr error;
|
||||
v.setException(std::make_exception_ptr(std::runtime_error("")));
|
||||
try {
|
||||
v.value();
|
||||
} catch (...) {
|
||||
hasException = true;
|
||||
error = std::current_exception();
|
||||
}
|
||||
ASSERT_TRUE(hasException);
|
||||
ASSERT_TRUE(v.hasError());
|
||||
|
||||
Try<void> ve = error;
|
||||
ASSERT_TRUE(ve.hasError());
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace ::testing;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleMock(&argc, argv);
|
||||
int ret = RUN_ALL_TESTS();
|
||||
return ret;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef __ASYNC_SIMPLE_UNITTEST_H
|
||||
#define __ASYNC_SIMPLE_UNITTEST_H
|
||||
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#define GTEST_USE_OWN_TR1_TUPLE 0
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class FUTURE_TESTBASE : public testing::Test {
|
||||
public:
|
||||
virtual void caseSetUp() = 0;
|
||||
virtual void caseTearDown() = 0;
|
||||
|
||||
void SetUp() override { caseSetUp(); }
|
||||
|
||||
void TearDown() override { caseTearDown(); }
|
||||
};
|
||||
|
||||
#endif //__ASYNC_SIMPLE_UNITTEST_H
|
|
@ -34,30 +34,26 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_ASYNC_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_ASYNC_H
|
||||
|
||||
#include <async_simple/uthread/Uthread.h>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include "async_simple/uthread/Uthread.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
||||
struct Launch {
|
||||
struct Prompt {};
|
||||
struct Schedule {};
|
||||
struct Current {};
|
||||
enum class Launch {
|
||||
Prompt,
|
||||
Schedule,
|
||||
Current,
|
||||
};
|
||||
|
||||
template <class T, class F,
|
||||
typename std::enable_if<std::is_same<T, Launch::Prompt>::value,
|
||||
T>::type* = nullptr>
|
||||
inline Uthread async(F&& f, Executor* ex) {
|
||||
template <Launch policy, class F>
|
||||
requires(policy == Launch::Prompt) inline Uthread async(F&& f, Executor* ex) {
|
||||
return Uthread(Attribute{ex}, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <class T, class F,
|
||||
typename std::enable_if<std::is_same<T, Launch::Schedule>::value,
|
||||
T>::type* = nullptr>
|
||||
inline void async(F&& f, Executor* ex) {
|
||||
template <Launch policy, class F>
|
||||
requires(policy == Launch::Schedule) inline void async(F&& f, Executor* ex) {
|
||||
if (!ex)
|
||||
AS_UNLIKELY { return; }
|
||||
ex->schedule([f = std::move(f), ex]() {
|
||||
|
@ -67,10 +63,9 @@ inline void async(F&& f, Executor* ex) {
|
|||
}
|
||||
|
||||
// schedule async task, set a callback
|
||||
template <class T, class F, class C,
|
||||
typename std::enable_if<std::is_same<T, Launch::Schedule>::value,
|
||||
T>::type* = nullptr>
|
||||
inline void async(F&& f, C&& c, Executor* ex) {
|
||||
template <Launch policy, class F, class C>
|
||||
requires(policy == Launch::Schedule) inline void async(F&& f, C&& c,
|
||||
Executor* ex) {
|
||||
if (!ex)
|
||||
AS_UNLIKELY { return; }
|
||||
ex->schedule([f = std::move(f), c = std::move(c), ex]() {
|
||||
|
@ -79,15 +74,53 @@ inline void async(F&& f, C&& c, Executor* ex) {
|
|||
});
|
||||
}
|
||||
|
||||
template <class T, class F,
|
||||
typename std::enable_if<std::is_same<T, Launch::Current>::value,
|
||||
T>::type* = nullptr>
|
||||
inline void async(F&& f, Executor* ex) {
|
||||
template <Launch policy, class F>
|
||||
requires(policy == Launch::Current) inline void async(F&& f, Executor* ex) {
|
||||
Uthread uth(Attribute{ex}, std::forward<F>(f));
|
||||
uth.detach();
|
||||
}
|
||||
|
||||
template <class F, class... Args,
|
||||
typename R = std::invoke_result_t<F&&, Args&&...>>
|
||||
inline Future<R> async(Launch policy, Attribute attr, F&& f, Args&&... args) {
|
||||
if (policy == Launch::Schedule) {
|
||||
if (!attr.ex)
|
||||
AS_UNLIKELY {
|
||||
// TODO log
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
Promise<R> p;
|
||||
auto rc = p.getFuture().via(attr.ex);
|
||||
auto proc = [p = std::move(p), ex = attr.ex, f = std::forward<F>(f),
|
||||
args =
|
||||
std::make_tuple(std::forward<Args>(args)...)]() mutable {
|
||||
if (ex) {
|
||||
p.forceSched().checkout();
|
||||
}
|
||||
if constexpr (std::is_void_v<R>) {
|
||||
std::apply(f, std::move(args));
|
||||
p.setValue();
|
||||
} else {
|
||||
p.setValue(std::apply(f, std::move(args)));
|
||||
}
|
||||
};
|
||||
if (policy == Launch::Schedule) {
|
||||
attr.ex->schedule([fn = std::move(proc), attr]() {
|
||||
Uthread(attr, std::move(fn)).detach();
|
||||
});
|
||||
} else if (policy == Launch::Current) {
|
||||
Uthread(attr, std::move(proc)).detach();
|
||||
} else {
|
||||
// TODO log
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace uthread
|
||||
|
||||
} // namespace async_simple
|
||||
|
||||
#endif // ASYNC_SIMPLE_UTHREAD_ASYNC_H
|
||||
#endif // ASYNC_SIMPLE_UTHREAD_ASYNC_H
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_AWAIT_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_AWAIT_H
|
||||
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/uthread/internal/thread_impl.h>
|
||||
#include <type_traits>
|
||||
#include "async_simple/Future.h"
|
||||
#include "async_simple/coro/Lazy.h"
|
||||
#include "async_simple/uthread/internal/thread_impl.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
@ -62,9 +62,8 @@ T await(Future<T>&& fut) {
|
|||
return f.value();
|
||||
}
|
||||
|
||||
// This await interface focus on await non-static member function of an object.
|
||||
// This await interface focus on await function of an object.
|
||||
// Here is an example:
|
||||
//
|
||||
// ```C++
|
||||
// class Foo {
|
||||
// public:
|
||||
|
@ -73,44 +72,31 @@ T await(Future<T>&& fut) {
|
|||
// Foo f;
|
||||
// await(ex, &Foo::bar, &f, Ts&&...);
|
||||
// ```
|
||||
template <class B, class Fn, class C, class... Ts>
|
||||
decltype(auto) await(Executor* ex, Fn B::*fn, C* cls, Ts&&... ts) {
|
||||
using ValueType =
|
||||
typename std::invoke_result_t<decltype(fn), C, Ts...>::ValueType;
|
||||
Promise<ValueType> p;
|
||||
auto f = p.getFuture().via(ex);
|
||||
auto lazy = [p = std::move(p), fn,
|
||||
cls](Ts&&... ts) mutable -> coro::Lazy<> {
|
||||
auto val = co_await (*cls.*fn)(ts...);
|
||||
p.setValue(std::move(val));
|
||||
co_return;
|
||||
};
|
||||
lazy(std::forward<Ts&&>(ts)...).setEx(ex).start([](auto&&) {});
|
||||
return await(std::move(f));
|
||||
}
|
||||
|
||||
// This await interface focus on await non-member functions. Here is the
|
||||
// example:
|
||||
//
|
||||
// ```C++
|
||||
// lazy<T> foo(Ts&&...);
|
||||
// await(ex, foo, Ts&&...);
|
||||
// auto lambda = [](Ts&&...) -> lazy<T> {};
|
||||
// await(ex, lambda, Ts&&...);
|
||||
// ```
|
||||
template <class Fn, class... Ts>
|
||||
decltype(auto) await(Executor* ex, Fn&& fn, Ts&&... ts) {
|
||||
using ValueType =
|
||||
typename std::invoke_result_t<decltype(fn), Ts...>::ValueType;
|
||||
template <class Fn, class... Args>
|
||||
decltype(auto) await(Executor* ex, Fn&& fn, Args&&... args) requires
|
||||
std::is_invocable_v<Fn&&, Args&&...> {
|
||||
using ValueType = typename std::invoke_result_t<Fn&&, Args&&...>::ValueType;
|
||||
Promise<ValueType> p;
|
||||
auto f = p.getFuture().via(ex);
|
||||
auto lazy = [p = std::move(p),
|
||||
fn = std::move(fn)](Ts&&... ts) mutable -> coro::Lazy<> {
|
||||
auto val = co_await fn(ts...);
|
||||
p.setValue(std::move(val));
|
||||
auto lazy =
|
||||
[p = std::move(p)]<typename... Ts>(Ts&&... ts) mutable -> coro::Lazy<> {
|
||||
if constexpr (std::is_void_v<ValueType>) {
|
||||
co_await std::invoke(std::forward<Ts>(ts)...);
|
||||
p.setValue();
|
||||
} else {
|
||||
p.setValue(co_await std::invoke(std::forward<Ts>(ts)...));
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
lazy(std::forward<Ts&&>(ts)...).setEx(ex).start([](auto&&) {});
|
||||
lazy(std::forward<Fn>(fn), std::forward<Args>(args)...)
|
||||
.setEx(ex)
|
||||
.start([](auto&&) {});
|
||||
return await(std::move(f));
|
||||
}
|
||||
|
||||
|
@ -130,6 +116,7 @@ T await(Executor* ex, Fn&& fn) {
|
|||
"Callable of await is not support, eg: Callable(Promise<T>)");
|
||||
Promise<T> p;
|
||||
auto f = p.getFuture().via(ex);
|
||||
p.forceSched().checkout();
|
||||
fn(std::move(p));
|
||||
return await(std::move(f));
|
||||
}
|
||||
|
|
|
@ -34,94 +34,78 @@
|
|||
* auto res2 = collectAll<Launch::Current>(v.begin(), v.end(), ex);
|
||||
* ```
|
||||
*
|
||||
* the type of res is std::vector<T>, the T is user task's return type.
|
||||
* `F` is a C++ lambda function, the type of returned value `value `is
|
||||
* `std::vector<T>`, `T` is the return type of `F`. If `T` is `void`,
|
||||
* `collectAll` would return `async_simple::Unit`.
|
||||
*/
|
||||
#ifndef ASYNC_SIMPLE_UTHREAD_COLLECT_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_COLLECT_H
|
||||
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/uthread/Async.h>
|
||||
#include <async_simple/uthread/Await.h>
|
||||
#include <type_traits>
|
||||
#include "async_simple/Future.h"
|
||||
#include "async_simple/uthread/Async.h"
|
||||
#include "async_simple/uthread/Await.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
||||
// TODO: Due to it is possible that the user of async_simple doesn't support
|
||||
// c++17, we didn't merge this two implementation by if constexpr. Merge them
|
||||
// once the codebases are ready to use c++17.
|
||||
template <class Policy, class Iterator>
|
||||
std::vector<typename std::enable_if<
|
||||
!std::is_void<std::invoke_result_t<
|
||||
typename std::iterator_traits<Iterator>::value_type>>::value,
|
||||
std::invoke_result_t<typename std::iterator_traits<Iterator>::value_type>>::
|
||||
type>
|
||||
collectAll(Iterator first, Iterator last, Executor* ex) {
|
||||
// TODO: Add Range version.
|
||||
template <Launch Policy, std::input_iterator Iterator>
|
||||
auto collectAll(Iterator first, Iterator last, Executor* ex) {
|
||||
assert(std::distance(first, last) >= 0);
|
||||
static_assert(!std::is_same<Launch::Prompt, Policy>::value,
|
||||
static_assert(Policy != Launch::Prompt,
|
||||
"collectAll not support Prompt launch policy");
|
||||
|
||||
using ResultType = std::invoke_result_t<
|
||||
using ValueType = std::invoke_result_t<
|
||||
typename std::iterator_traits<Iterator>::value_type>;
|
||||
constexpr bool IfReturnVoid = std::is_void_v<ValueType>;
|
||||
using ResultType =
|
||||
std::conditional_t<IfReturnVoid, void, std::vector<ValueType>>;
|
||||
|
||||
struct Context {
|
||||
#ifndef NDEBUG
|
||||
std::atomic<std::size_t> tasks;
|
||||
std::vector<ResultType> result;
|
||||
Promise<std::vector<ResultType>> promise;
|
||||
#endif
|
||||
std::conditional_t<IfReturnVoid, bool, ResultType> result;
|
||||
Promise<ResultType> promise;
|
||||
|
||||
Context(std::size_t n, Promise<std::vector<ResultType>>&& pr)
|
||||
: tasks(n), result(n), promise(pr) {}
|
||||
Context(std::size_t n, Promise<ResultType>&& pr)
|
||||
:
|
||||
#ifndef NDEBUG
|
||||
tasks(n),
|
||||
#endif
|
||||
promise(pr) {
|
||||
if constexpr (!IfReturnVoid)
|
||||
result.resize(n);
|
||||
}
|
||||
|
||||
~Context() {
|
||||
#ifndef NDEBUG
|
||||
assert(tasks == 0);
|
||||
#endif
|
||||
if constexpr (IfReturnVoid)
|
||||
promise.setValue();
|
||||
else
|
||||
promise.setValue(std::move(result));
|
||||
}
|
||||
};
|
||||
|
||||
return await<std::vector<ResultType>>(
|
||||
ex, [first, last, ex](Promise<std::vector<ResultType>>&& pr) mutable {
|
||||
auto n = static_cast<std::size_t>(std::distance(first, last));
|
||||
auto context = std::make_shared<Context>(n, std::move(pr));
|
||||
for (auto i = 0; first != last; ++i, ++first) {
|
||||
async<Policy>(
|
||||
[context, i, f = std::move(*first)]() mutable {
|
||||
context->result[i] = std::move(f());
|
||||
auto lastTasks = context->tasks.fetch_sub(
|
||||
1u, std::memory_order_acq_rel);
|
||||
if (lastTasks == 1u) {
|
||||
context->promise.setValue(
|
||||
std::move(context->result));
|
||||
}
|
||||
},
|
||||
ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <class Policy, class Iterator>
|
||||
typename std::enable_if<
|
||||
std::is_void<std::invoke_result_t<
|
||||
typename std::iterator_traits<Iterator>::value_type>>::value,
|
||||
void>::type
|
||||
collectAll(Iterator first, Iterator last, Executor* ex) {
|
||||
assert(std::distance(first, last) >= 0);
|
||||
static_assert(!std::is_same<Launch::Prompt, Policy>::value,
|
||||
"collectN not support Prompt launch policy");
|
||||
|
||||
struct Context {
|
||||
std::atomic<std::size_t> tasks;
|
||||
Promise<bool> promise;
|
||||
|
||||
Context(std::size_t n, Promise<bool>&& pr) : tasks(n), promise(pr) {}
|
||||
};
|
||||
|
||||
await<bool>(ex, [first, last, ex](Promise<bool>&& pr) mutable {
|
||||
return await<ResultType>(ex, [first, last,
|
||||
ex](Promise<ResultType>&& pr) mutable {
|
||||
auto n = static_cast<std::size_t>(std::distance(first, last));
|
||||
auto context = std::make_shared<Context>(n, std::move(pr));
|
||||
for (; first != last; ++first) {
|
||||
for (auto i = 0; first != last; ++i, ++first) {
|
||||
async<Policy>(
|
||||
[context, f = std::move(*first)]() mutable {
|
||||
f();
|
||||
auto lastTasks =
|
||||
context->tasks.fetch_sub(1u, std::memory_order_acq_rel);
|
||||
if (lastTasks == 1u) {
|
||||
context->promise.setValue(true);
|
||||
[context, i, f = std::move(*first)]() mutable {
|
||||
if constexpr (IfReturnVoid) {
|
||||
f();
|
||||
(void)i;
|
||||
} else {
|
||||
context->result[i] = std::move(f());
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
context->tasks.fetch_sub(1u, std::memory_order_acq_rel);
|
||||
#endif
|
||||
},
|
||||
ex);
|
||||
}
|
||||
|
@ -131,4 +115,4 @@ collectAll(Iterator first, Iterator last, Executor* ex) {
|
|||
} // namespace uthread
|
||||
} // namespace async_simple
|
||||
|
||||
#endif // ASYNC_SIMPLE_UTHREAD_COLLECT_H
|
||||
#endif // ASYNC_SIMPLE_UTHREAD_COLLECT_H
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_LATCH_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_LATCH_H
|
||||
|
||||
#include <async_simple/Future.h>
|
||||
#include <type_traits>
|
||||
#include "async_simple/Future.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_UTHREAD_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_UTHREAD_H
|
||||
|
||||
#include <async_simple/uthread/internal/thread.h>
|
||||
#include <memory>
|
||||
#include "async_simple/uthread/internal/thread.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
struct Attribute {
|
||||
Executor* ex;
|
||||
size_t stack_size = 0;
|
||||
};
|
||||
|
||||
// A Uthread is a stackful coroutine which would checkin/checkout based on
|
||||
|
@ -42,7 +42,8 @@ public:
|
|||
Uthread() = default;
|
||||
template <class Func>
|
||||
Uthread(Attribute attr, Func&& func) : _attr(std::move(attr)) {
|
||||
_ctx = std::make_unique<internal::thread_context>(std::move(func));
|
||||
_ctx = std::make_unique<internal::thread_context>(std::move(func),
|
||||
_attr.stack_size);
|
||||
}
|
||||
~Uthread() = default;
|
||||
Uthread(Uthread&& x) noexcept = default;
|
||||
|
|
109
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/jump_arm64_aapcs_macho_gas.S
vendored
Normal file
109
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/jump_arm64_aapcs_macho_gas.S
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright Edward Nevill + Oliver Kowalke 2015
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d8 | d9 | d10 | d11 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d12 | d13 | d14 | d15 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x19 | x20 | x21 | x22 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x23 | x24 | x25 | x26 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x27 | x28 | FP | LR | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | | | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0xa0| 0xa4| 0xa8| 0xac| | | *
|
||||
* ------------------------------------------------- *
|
||||
* | PC | align | | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.text
|
||||
.globl __fl_jump_fcontext
|
||||
.balign 16
|
||||
__fl_jump_fcontext:
|
||||
; prepare stack for GP + FPU
|
||||
sub sp, sp, #0xb0
|
||||
|
||||
; save d8 - d15
|
||||
stp d8, d9, [sp, #0x00]
|
||||
stp d10, d11, [sp, #0x10]
|
||||
stp d12, d13, [sp, #0x20]
|
||||
stp d14, d15, [sp, #0x30]
|
||||
|
||||
; save x19-x30
|
||||
stp x19, x20, [sp, #0x40]
|
||||
stp x21, x22, [sp, #0x50]
|
||||
stp x23, x24, [sp, #0x60]
|
||||
stp x25, x26, [sp, #0x70]
|
||||
stp x27, x28, [sp, #0x80]
|
||||
stp fp, lr, [sp, #0x90]
|
||||
|
||||
; save LR as PC
|
||||
str lr, [sp, #0xa0]
|
||||
|
||||
; store RSP (pointing to context-data) in X0
|
||||
mov x4, sp
|
||||
|
||||
; restore RSP (pointing to context-data) from X1
|
||||
mov sp, x0
|
||||
|
||||
; load d8 - d15
|
||||
ldp d8, d9, [sp, #0x00]
|
||||
ldp d10, d11, [sp, #0x10]
|
||||
ldp d12, d13, [sp, #0x20]
|
||||
ldp d14, d15, [sp, #0x30]
|
||||
|
||||
; load x19-x30
|
||||
ldp x19, x20, [sp, #0x40]
|
||||
ldp x21, x22, [sp, #0x50]
|
||||
ldp x23, x24, [sp, #0x60]
|
||||
ldp x25, x26, [sp, #0x70]
|
||||
ldp x27, x28, [sp, #0x80]
|
||||
ldp fp, lr, [sp, #0x90]
|
||||
|
||||
; return transfer_t from jump
|
||||
; pass transfer_t as first arg in context function
|
||||
; X0 == FCTX, X1 == DATA
|
||||
mov x0, x4
|
||||
|
||||
; load pc
|
||||
ldr x4, [sp, #0xa0]
|
||||
|
||||
; restore stack from GP + FPU
|
||||
add sp, sp, #0xb0
|
||||
|
||||
ret x4
|
83
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/make_arm64_aapcs_macho_gas.S
vendored
Normal file
83
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/make_arm64_aapcs_macho_gas.S
vendored
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright Edward Nevill + Oliver Kowalke 2015
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d8 | d9 | d10 | d11 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d12 | d13 | d14 | d15 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x19 | x20 | x21 | x22 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x23 | x24 | x25 | x26 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x27 | x28 | FP | LR | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | | | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0xa0| 0xa4| 0xa8| 0xac| | | *
|
||||
* ------------------------------------------------- *
|
||||
* | PC | align | | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.text
|
||||
.globl __fl_make_fcontext
|
||||
.balign 16
|
||||
|
||||
__fl_make_fcontext:
|
||||
; shift address in x0 (allocated stack) to lower 16 byte boundary
|
||||
and x0, x0, ~0xF
|
||||
|
||||
; reserve space for context-data on context-stack
|
||||
sub x0, x0, #0xb0
|
||||
|
||||
; third arg of make_fcontext() == address of context-function
|
||||
; store address as a PC to jump in
|
||||
str x2, [x0, #0xa0]
|
||||
|
||||
adr x1, finish
|
||||
|
||||
; save address of finish as return-address for context-function
|
||||
; will be entered after context-function returns (LR register)
|
||||
str x1, [x0, #0x98]
|
||||
|
||||
ret lr ; return pointer to context-data (x0)
|
||||
|
||||
finish:
|
||||
; exit code is zero
|
||||
mov x0, #0
|
||||
; exit application
|
||||
bl __exit
|
||||
|
||||
|
108
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/ontop_arm64_aapcs_macho_gas.S
vendored
Normal file
108
thirdparty/async_simple/async_simple/uthread/internal/Darwin/arm64/ontop_arm64_aapcs_macho_gas.S
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright Edward Nevill + Oliver Kowalke 2015
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d8 | d9 | d10 | d11 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| *
|
||||
* ------------------------------------------------- *
|
||||
* | d12 | d13 | d14 | d15 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x19 | x20 | x21 | x22 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x60| 0x64| 0x68| 0x6c| 0x70| 0x74| 0x78| 0x7c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x23 | x24 | x25 | x26 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0x80| 0x84| 0x88| 0x8c| 0x90| 0x94| 0x98| 0x9c| *
|
||||
* ------------------------------------------------- *
|
||||
* | x27 | x28 | FP | LR | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | | | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0xa0| 0xa4| 0xa8| 0xac| | | *
|
||||
* ------------------------------------------------- *
|
||||
* | PC | align | | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.text
|
||||
.global __fl_ontop_fcontext
|
||||
.balign 16
|
||||
__fl_ontop_fcontext:
|
||||
; prepare stack for GP + FPU
|
||||
sub sp, sp, #0xb0
|
||||
|
||||
; save d8 - d15
|
||||
stp d8, d9, [sp, #0x00]
|
||||
stp d10, d11, [sp, #0x10]
|
||||
stp d12, d13, [sp, #0x20]
|
||||
stp d14, d15, [sp, #0x30]
|
||||
|
||||
; save x19-x30
|
||||
stp x19, x20, [sp, #0x40]
|
||||
stp x21, x22, [sp, #0x50]
|
||||
stp x23, x24, [sp, #0x60]
|
||||
stp x25, x26, [sp, #0x70]
|
||||
stp x27, x28, [sp, #0x80]
|
||||
stp x29, x30, [sp, #0x90]
|
||||
|
||||
; save LR as PC
|
||||
str x30, [sp, #0xa0]
|
||||
|
||||
; store RSP (pointing to context-data) in X5
|
||||
mov x4, sp
|
||||
|
||||
; restore RSP (pointing to context-data) from X1
|
||||
mov sp, x0
|
||||
|
||||
; load d8 - d15
|
||||
ldp d8, d9, [sp, #0x00]
|
||||
ldp d10, d11, [sp, #0x10]
|
||||
ldp d12, d13, [sp, #0x20]
|
||||
ldp d14, d15, [sp, #0x30]
|
||||
|
||||
; load x19-x30
|
||||
ldp x19, x20, [sp, #0x40]
|
||||
ldp x21, x22, [sp, #0x50]
|
||||
ldp x23, x24, [sp, #0x60]
|
||||
ldp x25, x26, [sp, #0x70]
|
||||
ldp x27, x28, [sp, #0x80]
|
||||
ldp x29, x30, [sp, #0x90]
|
||||
|
||||
; return transfer_t from jump
|
||||
; pass transfer_t as first arg in context function
|
||||
; X0 == FCTX, X1 == DATA
|
||||
mov x0, x4
|
||||
|
||||
; skip pc
|
||||
; restore stack from GP + FPU
|
||||
add sp, sp, #0xb0
|
||||
|
||||
; jump to ontop-function
|
||||
ret x2
|
75
thirdparty/async_simple/async_simple/uthread/internal/Darwin/x86_64/jump_x86_64_sysv_macho_gas.S
vendored
Normal file
75
thirdparty/async_simple/async_simple/uthread/internal/Darwin/x86_64/jump_x86_64_sysv_macho_gas.S
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | R15 | RBX | RBP | RIP | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
.text
|
||||
.globl __fl_jump_fcontext
|
||||
.align 8
|
||||
__fl_jump_fcontext:
|
||||
leaq -0x38(%rsp), %rsp /* prepare stack */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
stmxcsr (%rsp) /* save MMX control- and status-word */
|
||||
fnstcw 0x4(%rsp) /* save x87 control-word */
|
||||
#endif
|
||||
|
||||
movq %r12, 0x8(%rsp) /* save R12 */
|
||||
movq %r13, 0x10(%rsp) /* save R13 */
|
||||
movq %r14, 0x18(%rsp) /* save R14 */
|
||||
movq %r15, 0x20(%rsp) /* save R15 */
|
||||
movq %rbx, 0x28(%rsp) /* save RBX */
|
||||
movq %rbp, 0x30(%rsp) /* save RBP */
|
||||
|
||||
/* store RSP (pointing to context-data) in RAX */
|
||||
movq %rsp, %rax
|
||||
|
||||
/* restore RSP (pointing to context-data) from RDI */
|
||||
movq %rdi, %rsp
|
||||
|
||||
movq 0x38(%rsp), %r8 /* restore return-address */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
ldmxcsr (%rsp) /* restore MMX control- and status-word */
|
||||
fldcw 0x4(%rsp) /* restore x87 control-word */
|
||||
#endif
|
||||
|
||||
movq 0x8(%rsp), %r12 /* restore R12 */
|
||||
movq 0x10(%rsp), %r13 /* restore R13 */
|
||||
movq 0x18(%rsp), %r14 /* restore R14 */
|
||||
movq 0x20(%rsp), %r15 /* restore R15 */
|
||||
movq 0x28(%rsp), %rbx /* restore RBX */
|
||||
movq 0x30(%rsp), %rbp /* restore RBP */
|
||||
|
||||
leaq 0x40(%rsp), %rsp /* prepare stack */
|
||||
|
||||
/* return transfer_t from jump */
|
||||
/* RAX == fctx, RDX == data */
|
||||
movq %rsi, %rdx
|
||||
/* pass transfer_t as first arg in context function */
|
||||
/* RDI == fctx, RSI == data */
|
||||
movq %rax, %rdi
|
||||
|
||||
/* indirect jump to context */
|
||||
jmp *%r8
|
76
thirdparty/async_simple/async_simple/uthread/internal/Darwin/x86_64/make_x86_64_sysv_macho_gas.S
vendored
Normal file
76
thirdparty/async_simple/async_simple/uthread/internal/Darwin/x86_64/make_x86_64_sysv_macho_gas.S
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | R15 | RBX | RBP | RIP | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
.text
|
||||
.globl __fl_make_fcontext
|
||||
.align 8
|
||||
__fl_make_fcontext:
|
||||
/* first arg of make_fcontext() == top of context-stack */
|
||||
movq %rdi, %rax
|
||||
|
||||
/* shift address in RAX to lower 16 byte boundary */
|
||||
andq $-16, %rax
|
||||
|
||||
/* reserve space for context-data on context-stack */
|
||||
/* on context-function entry: (RSP -0x8) % 16 == 0 */
|
||||
leaq -0x40(%rax), %rax
|
||||
|
||||
/* third arg of make_fcontext() == address of context-function */
|
||||
/* stored in RBX */
|
||||
movq %rdx, 0x28(%rax)
|
||||
|
||||
/* save MMX control- and status-word */
|
||||
stmxcsr (%rax)
|
||||
/* save x87 control-word */
|
||||
fnstcw 0x4(%rax)
|
||||
|
||||
/* compute abs address of label trampoline */
|
||||
leaq trampoline(%rip), %rcx
|
||||
/* save address of trampoline as return-address for context-function */
|
||||
/* will be entered after calling jump_fcontext() first time */
|
||||
movq %rcx, 0x38(%rax)
|
||||
|
||||
/* compute abs address of label finish */
|
||||
leaq finish(%rip), %rcx
|
||||
/* save address of finish as return-address for context-function */
|
||||
/* will be entered after context-function returns */
|
||||
movq %rcx, 0x30(%rax)
|
||||
|
||||
ret /* return pointer to context-data */
|
||||
|
||||
trampoline:
|
||||
/* store return address on stack */
|
||||
/* fix stack alignment */
|
||||
push %rbp
|
||||
/* jump to context-function */
|
||||
jmp *%rbx
|
||||
|
||||
finish:
|
||||
/* exit code is zero */
|
||||
xorq %rdi, %rdi
|
||||
/* exit application */
|
||||
call __exit
|
||||
hlt
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/****************************************************************************************
|
||||
* *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* | R15 | RBX | RBP | RIP | *
|
||||
* ---------------------------------------------------------------------------------- *
|
||||
* *
|
||||
****************************************************************************************/
|
||||
|
||||
.text
|
||||
.globl __fl_ontop_fcontext
|
||||
.align 8
|
||||
__fl_ontop_fcontext:
|
||||
/* preserve ontop-function in R8 */
|
||||
movq %rdx, %r8
|
||||
|
||||
leaq -0x38(%rsp), %rsp /* prepare stack */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
stmxcsr (%rsp) /* save MMX control- and status-word */
|
||||
fnstcw 0x4(%rsp) /* save x87 control-word */
|
||||
#endif
|
||||
|
||||
movq %r12, 0x8(%rsp) /* save R12 */
|
||||
movq %r13, 0x10(%rsp) /* save R13 */
|
||||
movq %r14, 0x18(%rsp) /* save R14 */
|
||||
movq %r15, 0x20(%rsp) /* save R15 */
|
||||
movq %rbx, 0x28(%rsp) /* save RBX */
|
||||
movq %rbp, 0x30(%rsp) /* save RBP */
|
||||
|
||||
/* store RSP (pointing to context-data) in RAX */
|
||||
movq %rsp, %rax
|
||||
|
||||
/* restore RSP (pointing to context-data) from RDI */
|
||||
movq %rdi, %rsp
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
ldmxcsr (%rsp) /* restore MMX control- and status-word */
|
||||
fldcw 0x4(%rsp) /* restore x87 control-word */
|
||||
#endif
|
||||
|
||||
movq 0x8(%rsp), %r12 /* restore R12 */
|
||||
movq 0x10(%rsp), %r13 /* restore R13 */
|
||||
movq 0x18(%rsp), %r14 /* restore R14 */
|
||||
movq 0x20(%rsp), %r15 /* restore R15 */
|
||||
movq 0x28(%rsp), %rbx /* restore RBX */
|
||||
movq 0x30(%rsp), %rbp /* restore RBP */
|
||||
|
||||
leaq 0x38(%rsp), %rsp /* prepare stack */
|
||||
|
||||
/* return transfer_t from jump */
|
||||
/* RAX == fctx, RDX == data */
|
||||
movq %rsi, %rdx
|
||||
/* pass transfer_t as first arg in context function */
|
||||
/* RDI == fctx, RSI == data */
|
||||
movq %rax, %rdi
|
||||
|
||||
/* keep return-address on stack */
|
||||
|
||||
/* indirect jump to context */
|
||||
jmp *%r8
|
221
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/jump_ppc64_sysv_elf_gas.S
vendored
Normal file
221
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/jump_ppc64_sysv_elf_gas.S
vendored
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC | R14 | R15 | R16 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R17 | R18 | R19 | R20 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R21 | R22 | R23 | R24 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R25 | R26 | R27 | R28 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R29 | R30 | R31 | hidden | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | *
|
||||
* ------------------------------------------------- *
|
||||
* | CR | LR | PC | back-chain| *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | *
|
||||
* ------------------------------------------------- *
|
||||
* | cr saved | lr saved | compiler | linker | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC saved | FCTX | DATA | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.file "jump_ppc64_sysv_elf_gas.S"
|
||||
.globl _fl_jump_fcontext
|
||||
#if _CALL_ELF == 2
|
||||
.text
|
||||
.align 2
|
||||
_fl_jump_fcontext:
|
||||
addis %r2, %r12, .TOC.-_fl_jump_fcontext@ha
|
||||
addi %r2, %r2, .TOC.-_fl_jump_fcontext@l
|
||||
.localentry _fl_jump_fcontext, . - _fl_jump_fcontext
|
||||
#else
|
||||
.section ".opd","aw"
|
||||
.align 3
|
||||
_fl_jump_fcontext:
|
||||
# ifdef _CALL_LINUX
|
||||
.quad .L._fl_jump_fcontext,.TOC.@tocbase,0
|
||||
.type _fl_jump_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
.L._fl_jump_fcontext:
|
||||
# else
|
||||
.hidden ._fl_jump_fcontext
|
||||
.globl ._fl_jump_fcontext
|
||||
.quad ._fl_jump_fcontext,.TOC.@tocbase,0
|
||||
.size _fl_jump_fcontext,24
|
||||
.type ._fl_jump_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
._fl_jump_fcontext:
|
||||
# endif
|
||||
#endif
|
||||
# reserve space on stack
|
||||
subi %r1, %r1, 184
|
||||
|
||||
#if _CALL_ELF != 2
|
||||
std %r2, 0(%r1) # save TOC
|
||||
#endif
|
||||
std %r14, 8(%r1) # save R14
|
||||
std %r15, 16(%r1) # save R15
|
||||
std %r16, 24(%r1) # save R16
|
||||
std %r17, 32(%r1) # save R17
|
||||
std %r18, 40(%r1) # save R18
|
||||
std %r19, 48(%r1) # save R19
|
||||
std %r20, 56(%r1) # save R20
|
||||
std %r21, 64(%r1) # save R21
|
||||
std %r22, 72(%r1) # save R22
|
||||
std %r23, 80(%r1) # save R23
|
||||
std %r24, 88(%r1) # save R24
|
||||
std %r25, 96(%r1) # save R25
|
||||
std %r26, 104(%r1) # save R26
|
||||
std %r27, 112(%r1) # save R27
|
||||
std %r28, 120(%r1) # save R28
|
||||
std %r29, 128(%r1) # save R29
|
||||
std %r30, 136(%r1) # save R30
|
||||
std %r31, 144(%r1) # save R31
|
||||
#if _CALL_ELF != 2
|
||||
std %r3, 152(%r1) # save hidden
|
||||
#endif
|
||||
|
||||
# save CR
|
||||
mfcr %r0
|
||||
std %r0, 160(%r1)
|
||||
# save LR
|
||||
mflr %r0
|
||||
std %r0, 168(%r1)
|
||||
# save LR as PC
|
||||
std %r0, 176(%r1)
|
||||
|
||||
# store RSP (pointing to context-data) in R6
|
||||
mr %r6, %r1
|
||||
|
||||
#if _CALL_ELF == 2
|
||||
# restore RSP (pointing to context-data) from R3
|
||||
mr %r1, %r3
|
||||
#else
|
||||
# restore RSP (pointing to context-data) from R4
|
||||
mr %r1, %r4
|
||||
|
||||
ld %r2, 0(%r1) # restore TOC
|
||||
#endif
|
||||
ld %r14, 8(%r1) # restore R14
|
||||
ld %r15, 16(%r1) # restore R15
|
||||
ld %r16, 24(%r1) # restore R16
|
||||
ld %r17, 32(%r1) # restore R17
|
||||
ld %r18, 40(%r1) # restore R18
|
||||
ld %r19, 48(%r1) # restore R19
|
||||
ld %r20, 56(%r1) # restore R20
|
||||
ld %r21, 64(%r1) # restore R21
|
||||
ld %r22, 72(%r1) # restore R22
|
||||
ld %r23, 80(%r1) # restore R23
|
||||
ld %r24, 88(%r1) # restore R24
|
||||
ld %r25, 96(%r1) # restore R25
|
||||
ld %r26, 104(%r1) # restore R26
|
||||
ld %r27, 112(%r1) # restore R27
|
||||
ld %r28, 120(%r1) # restore R28
|
||||
ld %r29, 128(%r1) # restore R29
|
||||
ld %r30, 136(%r1) # restore R30
|
||||
ld %r31, 144(%r1) # restore R31
|
||||
#if _CALL_ELF != 2
|
||||
ld %r3, 152(%r1) # restore hidden
|
||||
#endif
|
||||
|
||||
# restore CR
|
||||
ld %r0, 160(%r1)
|
||||
mtcr %r0
|
||||
# restore LR
|
||||
ld %r0, 168(%r1)
|
||||
mtlr %r0
|
||||
|
||||
# load PC
|
||||
ld %r12, 176(%r1)
|
||||
# restore CTR
|
||||
mtctr %r12
|
||||
|
||||
# adjust stack
|
||||
addi %r1, %r1, 184
|
||||
|
||||
#if _CALL_ELF == 2
|
||||
# copy transfer_t into transfer_fn arg registers
|
||||
mr %r3, %r6
|
||||
# arg pointer already in %r4
|
||||
|
||||
# jump to context
|
||||
bctr
|
||||
.size _fl_jump_fcontext, .-_fl_jump_fcontext
|
||||
#else
|
||||
# zero in r3 indicates first jump to context-function
|
||||
cmpdi %r3, 0
|
||||
beq use_entry_arg
|
||||
|
||||
# return transfer_t
|
||||
std %r6, 0(%r3)
|
||||
std %r5, 8(%r3)
|
||||
|
||||
# jump to context
|
||||
bctr
|
||||
|
||||
use_entry_arg:
|
||||
# copy transfer_t into transfer_fn arg registers
|
||||
mr %r3, %r6
|
||||
mr %r4, %r5
|
||||
|
||||
# jump to context
|
||||
bctr
|
||||
# ifdef _CALL_LINUX
|
||||
.size ._fl_jump_fcontext, .-.L._fl_jump_fcontext
|
||||
# else
|
||||
.size ._fl_jump_fcontext, .-._fl_jump_fcontext
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Mark that we don't need executable stack. */
|
||||
.section .note.GNU-stack,"",%progbits
|
177
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/make_ppc64_sysv_elf_gas.S
vendored
Normal file
177
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/make_ppc64_sysv_elf_gas.S
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC | R14 | R15 | R16 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R17 | R18 | R19 | R20 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R21 | R22 | R23 | R24 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R25 | R26 | R27 | R28 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R29 | R30 | R31 | hidden | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | *
|
||||
* ------------------------------------------------- *
|
||||
* | CR | LR | PC | back-chain| *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | *
|
||||
* ------------------------------------------------- *
|
||||
* | cr saved | lr saved | compiler | linker | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC saved | FCTX | DATA | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.file "make_ppc64_sysv_elf_gas.S"
|
||||
.globl _fl_make_fcontext
|
||||
#if _CALL_ELF == 2
|
||||
.text
|
||||
.align 2
|
||||
_fl_make_fcontext:
|
||||
addis %r2, %r12, .TOC.-_fl_make_fcontext@ha
|
||||
addi %r2, %r2, .TOC.-_fl_make_fcontext@l
|
||||
.localentry _fl_make_fcontext, . - _fl_make_fcontext
|
||||
#else
|
||||
.section ".opd","aw"
|
||||
.align 3
|
||||
_fl_make_fcontext:
|
||||
# ifdef _CALL_LINUX
|
||||
.quad .L._fl_make_fcontext,.TOC.@tocbase,0
|
||||
.type _fl_make_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
.L._fl_make_fcontext:
|
||||
# else
|
||||
.hidden ._fl_make_fcontext
|
||||
.globl ._fl_make_fcontext
|
||||
.quad ._fl_make_fcontext,.TOC.@tocbase,0
|
||||
.size _fl_make_fcontext,24
|
||||
.type ._fl_make_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
._fl_make_fcontext:
|
||||
# endif
|
||||
#endif
|
||||
# save return address into R6
|
||||
mflr %r6
|
||||
|
||||
# first arg of _fl_make_fcontext() == top address of context-stack
|
||||
# shift address in R3 to lower 16 byte boundary
|
||||
clrrdi %r3, %r3, 4
|
||||
|
||||
# reserve space for context-data on context-stack
|
||||
# including 64 byte of linkage + parameter area (R1 % 16 == 0)
|
||||
subi %r3, %r3, 248
|
||||
|
||||
# third arg of _fl_make_fcontext() == address of context-function
|
||||
# entry point (ELFv2) or descriptor (ELFv1)
|
||||
#if _CALL_ELF == 2
|
||||
# save address of context-function entry point
|
||||
std %r5, 176(%r3)
|
||||
#else
|
||||
# save address of context-function entry point
|
||||
ld %r4, 0(%r5)
|
||||
std %r4, 176(%r3)
|
||||
# save TOC of context-function
|
||||
ld %r4, 8(%r5)
|
||||
std %r4, 0(%r3)
|
||||
#endif
|
||||
|
||||
# set back-chain to zero
|
||||
li %r0, 0
|
||||
std %r0, 184(%r3)
|
||||
|
||||
#if _CALL_ELF != 2
|
||||
# zero in r3 indicates first jump to context-function
|
||||
std %r0, 152(%r3)
|
||||
#endif
|
||||
|
||||
# load LR
|
||||
mflr %r0
|
||||
# jump to label 1
|
||||
bl 1f
|
||||
1:
|
||||
# load LR into R4
|
||||
mflr %r4
|
||||
# compute abs address of label finish
|
||||
addi %r4, %r4, finish - 1b
|
||||
# restore LR
|
||||
mtlr %r0
|
||||
# save address of finish as return-address for context-function
|
||||
# will be entered after context-function returns
|
||||
std %r4, 168(%r3)
|
||||
|
||||
# restore return address from R6
|
||||
mtlr %r6
|
||||
|
||||
blr # return pointer to context-data
|
||||
|
||||
finish:
|
||||
# save return address into R0
|
||||
mflr %r0
|
||||
# save return address on stack, set up stack frame
|
||||
std %r0, 8(%r1)
|
||||
# allocate stack space, R1 % 16 == 0
|
||||
stdu %r1, -32(%r1)
|
||||
|
||||
# exit code is zero
|
||||
li %r3, 0
|
||||
# exit application
|
||||
bl _exit
|
||||
nop
|
||||
#if _CALL_ELF == 2
|
||||
.size _fl_make_fcontext, .-_fl_make_fcontext
|
||||
#else
|
||||
# ifdef _CALL_LINUX
|
||||
.size ._fl_make_fcontext, .-.L._fl_make_fcontext
|
||||
# else
|
||||
.size ._fl_make_fcontext, .-._fl_make_fcontext
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Mark that we don't need executable stack. */
|
||||
.section .note.GNU-stack,"",%progbits
|
244
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/ontop_ppc64_sysv_elf_gas.S
vendored
Normal file
244
thirdparty/async_simple/async_simple/uthread/internal/Linux/ppc64le/ontop_ppc64_sysv_elf_gas.S
vendored
Normal file
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
Copyright Oliver Kowalke 2009.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC | R14 | R15 | R16 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R17 | R18 | R19 | R20 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R21 | R22 | R23 | R24 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R25 | R26 | R27 | R28 | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | *
|
||||
* ------------------------------------------------- *
|
||||
* | R29 | R30 | R31 | hidden | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | *
|
||||
* ------------------------------------------------- *
|
||||
* | CR | LR | PC | back-chain| *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | *
|
||||
* ------------------------------------------------- *
|
||||
* | cr saved | lr saved | compiler | linker | *
|
||||
* ------------------------------------------------- *
|
||||
* ------------------------------------------------- *
|
||||
* | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | *
|
||||
* ------------------------------------------------- *
|
||||
* | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | *
|
||||
* ------------------------------------------------- *
|
||||
* | TOC saved | FCTX | DATA | | *
|
||||
* ------------------------------------------------- *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
.file "ontop_ppc64_sysv_elf_gas.S"
|
||||
.globl _fl_ontop_fcontext
|
||||
#if _CALL_ELF == 2
|
||||
.text
|
||||
.align 2
|
||||
_fl_ontop_fcontext:
|
||||
addis %r2, %r12, .TOC.-_fl_ontop_fcontext@ha
|
||||
addi %r2, %r2, .TOC.-_fl_ontop_fcontext@l
|
||||
.localentry _fl_ontop_fcontext, . - _fl_ontop_fcontext
|
||||
#else
|
||||
.section ".opd","aw"
|
||||
.align 3
|
||||
_fl_ontop_fcontext:
|
||||
# ifdef _CALL_LINUX
|
||||
.quad .L._fl_ontop_fcontext,.TOC.@tocbase,0
|
||||
.type _fl_ontop_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
.L._fl_ontop_fcontext:
|
||||
# else
|
||||
.hidden ._fl_ontop_fcontext
|
||||
.globl ._fl_ontop_fcontext
|
||||
.quad ._fl_ontop_fcontext,.TOC.@tocbase,0
|
||||
.size _fl_ontop_fcontext,24
|
||||
.type ._fl_ontop_fcontext,@function
|
||||
.text
|
||||
.align 2
|
||||
._fl_ontop_fcontext:
|
||||
# endif
|
||||
#endif
|
||||
# reserve space on stack
|
||||
subi %r1, %r1, 184
|
||||
|
||||
#if _CALL_ELF != 2
|
||||
std %r2, 0(%r1) # save TOC
|
||||
#endif
|
||||
std %r14, 8(%r1) # save R14
|
||||
std %r15, 16(%r1) # save R15
|
||||
std %r16, 24(%r1) # save R16
|
||||
std %r17, 32(%r1) # save R17
|
||||
std %r18, 40(%r1) # save R18
|
||||
std %r19, 48(%r1) # save R19
|
||||
std %r20, 56(%r1) # save R20
|
||||
std %r21, 64(%r1) # save R21
|
||||
std %r22, 72(%r1) # save R22
|
||||
std %r23, 80(%r1) # save R23
|
||||
std %r24, 88(%r1) # save R24
|
||||
std %r25, 96(%r1) # save R25
|
||||
std %r26, 104(%r1) # save R26
|
||||
std %r27, 112(%r1) # save R27
|
||||
std %r28, 120(%r1) # save R28
|
||||
std %r29, 128(%r1) # save R29
|
||||
std %r30, 136(%r1) # save R30
|
||||
std %r31, 144(%r1) # save R31
|
||||
#if _CALL_ELF != 2
|
||||
std %r3, 152(%r1) # save hidden
|
||||
#endif
|
||||
|
||||
# save CR
|
||||
mfcr %r0
|
||||
std %r0, 160(%r1)
|
||||
# save LR
|
||||
mflr %r0
|
||||
std %r0, 168(%r1)
|
||||
# save LR as PC
|
||||
std %r0, 176(%r1)
|
||||
|
||||
# store RSP (pointing to context-data) in R7
|
||||
mr %r7, %r1
|
||||
|
||||
#if _CALL_ELF == 2
|
||||
# restore RSP (pointing to context-data) from R3
|
||||
mr %r1, %r3
|
||||
#else
|
||||
# restore RSP (pointing to context-data) from R4
|
||||
mr %r1, %r4
|
||||
#endif
|
||||
|
||||
ld %r14, 8(%r1) # restore R14
|
||||
ld %r15, 16(%r1) # restore R15
|
||||
ld %r16, 24(%r1) # restore R16
|
||||
ld %r17, 32(%r1) # restore R17
|
||||
ld %r18, 40(%r1) # restore R18
|
||||
ld %r19, 48(%r1) # restore R19
|
||||
ld %r20, 56(%r1) # restore R20
|
||||
ld %r21, 64(%r1) # restore R21
|
||||
ld %r22, 72(%r1) # restore R22
|
||||
ld %r23, 80(%r1) # restore R23
|
||||
ld %r24, 88(%r1) # restore R24
|
||||
ld %r25, 96(%r1) # restore R25
|
||||
ld %r26, 104(%r1) # restore R26
|
||||
ld %r27, 112(%r1) # restore R27
|
||||
ld %r28, 120(%r1) # restore R28
|
||||
ld %r29, 128(%r1) # restore R29
|
||||
ld %r30, 136(%r1) # restore R30
|
||||
ld %r31, 144(%r1) # restore R31
|
||||
#if _CALL_ELF != 2
|
||||
ld %r3, 152(%r1) # restore hidden
|
||||
#endif
|
||||
|
||||
# restore CR
|
||||
ld %r0, 160(%r1)
|
||||
mtcr %r0
|
||||
|
||||
#if _CALL_ELF == 2
|
||||
# restore CTR
|
||||
mtctr %r5
|
||||
|
||||
# store cb entrypoint in %r12, used for TOC calculation
|
||||
mr %r12, %r5
|
||||
|
||||
# copy transfer_t into ontop_fn arg registers
|
||||
mr %r3, %r7
|
||||
# arg pointer already in %r4
|
||||
#else
|
||||
# copy transfer_t into ontop_fn arg registers
|
||||
mr %r4, %r7
|
||||
# arg pointer already in %r5
|
||||
# hidden arg already in %r3
|
||||
|
||||
# restore CTR
|
||||
ld %r7, 0(%r6)
|
||||
mtctr %r7
|
||||
# restore TOC
|
||||
ld %r2, 8(%r6)
|
||||
|
||||
# zero in r3 indicates first jump to context-function
|
||||
cmpdi %r3, 0
|
||||
beq use_entry_arg
|
||||
#endif
|
||||
|
||||
return_to_ctx:
|
||||
# restore LR
|
||||
ld %r0, 168(%r1)
|
||||
mtlr %r0
|
||||
|
||||
# adjust stack
|
||||
addi %r1, %r1, 184
|
||||
|
||||
# jump to context
|
||||
bctr
|
||||
|
||||
#if _CALL_ELF == 2
|
||||
.size _fl_ontop_fcontext, .-_fl_ontop_fcontext
|
||||
#else
|
||||
use_entry_arg:
|
||||
# compute return-value struct address
|
||||
# (passed has hidden arg to ontop_fn)
|
||||
addi %r3, %r1, 8
|
||||
|
||||
# jump to context and update LR
|
||||
bctrl
|
||||
|
||||
# restore CTR
|
||||
ld %r7, 176(%r1)
|
||||
mtctr %r7
|
||||
#if _CALL_ELF != 2
|
||||
# restore TOC
|
||||
ld %r2, 0(%r1)
|
||||
#endif
|
||||
|
||||
# copy returned transfer_t into entry_fn arg registers
|
||||
ld %r3, 8(%r1)
|
||||
ld %r4, 16(%r1)
|
||||
|
||||
b return_to_ctx
|
||||
# ifdef _CALL_LINUX
|
||||
.size ._fl_ontop_fcontext, .-.L._fl_ontop_fcontext
|
||||
# else
|
||||
.size ._fl_ontop_fcontext, .-._fl_ontop_fcontext
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/* Mark that we don't need executable stack. */
|
||||
.section .note.GNU-stack,"",%progbits
|
|
@ -31,8 +31,10 @@
|
|||
_fl_jump_fcontext:
|
||||
leaq -0x38(%rsp), %rsp /* prepare stack */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
stmxcsr (%rsp) /* save MMX control- and status-word */
|
||||
fnstcw 0x4(%rsp) /* save x87 control-word */
|
||||
#endif
|
||||
|
||||
movq %r12, 0x8(%rsp) /* save R12 */
|
||||
movq %r13, 0x10(%rsp) /* save R13 */
|
||||
|
@ -49,8 +51,10 @@ _fl_jump_fcontext:
|
|||
|
||||
movq 0x38(%rsp), %r8 /* restore return-address */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
ldmxcsr (%rsp) /* restore MMX control- and status-word */
|
||||
fldcw 0x4(%rsp) /* restore x87 control-word */
|
||||
#endif
|
||||
|
||||
movq 0x8(%rsp), %r12 /* restore R12 */
|
||||
movq 0x10(%rsp), %r13 /* restore R13 */
|
|
@ -34,8 +34,10 @@ _fl_ontop_fcontext:
|
|||
|
||||
leaq -0x38(%rsp), %rsp /* prepare stack */
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
stmxcsr (%rsp) /* save MMX control- and status-word */
|
||||
fnstcw 0x4(%rsp) /* save x87 control-word */
|
||||
#endif
|
||||
|
||||
movq %r12, 0x8(%rsp) /* save R12 */
|
||||
movq %r13, 0x10(%rsp) /* save R13 */
|
||||
|
@ -50,8 +52,10 @@ _fl_ontop_fcontext:
|
|||
/* restore RSP (pointing to context-data) from RDI */
|
||||
movq %rdi, %rsp
|
||||
|
||||
#if !defined(ASYNC_SIMPLE_USE_TSX)
|
||||
ldmxcsr (%rsp) /* restore MMX control- and status-word */
|
||||
fldcw 0x4(%rsp) /* restore x87 control-word */
|
||||
#endif
|
||||
|
||||
movq 0x8(%rsp), %r12 /* restore R12 */
|
||||
movq 0x10(%rsp), %r13 /* restore R13 */
|
|
@ -20,17 +20,46 @@
|
|||
* Copyright (C) 2015 Cloudius Systems, Ltd.
|
||||
*/
|
||||
|
||||
#include <ucontext.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/uthread/internal/thread.h>
|
||||
#include "async_simple/Common.h"
|
||||
#include "async_simple/uthread/internal/thread.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
namespace internal {
|
||||
|
||||
#ifdef AS_INTERNAL_USE_ASAN
|
||||
|
||||
extern "C" {
|
||||
void __sanitizer_start_switch_fiber(void** fake_stack_save,
|
||||
const void* stack_bottom,
|
||||
size_t stack_size);
|
||||
void __sanitizer_finish_switch_fiber(void* fake_stack_save,
|
||||
const void** stack_bottom_old,
|
||||
size_t* stack_size_old);
|
||||
}
|
||||
|
||||
inline void start_switch_fiber(jmp_buf_link* context) {
|
||||
__sanitizer_start_switch_fiber(&context->asan_fake_stack,
|
||||
context->asan_stack_bottom,
|
||||
context->asan_stack_size);
|
||||
}
|
||||
|
||||
inline void finish_switch_fiber(jmp_buf_link* context) {
|
||||
__sanitizer_finish_switch_fiber(context->asan_fake_stack,
|
||||
&context->asan_stack_bottom,
|
||||
&context->asan_stack_size);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline void start_switch_fiber(jmp_buf_link* context) {}
|
||||
inline void finish_switch_fiber(jmp_buf_link* context) {}
|
||||
|
||||
#endif // AS_INTERNAL_USE_ASAN
|
||||
|
||||
thread_local jmp_buf_link g_unthreaded_context;
|
||||
thread_local jmp_buf_link* g_current_context = nullptr;
|
||||
|
||||
|
@ -56,19 +85,45 @@ inline void jmp_buf_link::switch_in() {
|
|||
link = std::exchange(g_current_context, this);
|
||||
if (!link)
|
||||
AS_UNLIKELY { link = &g_unthreaded_context; }
|
||||
start_switch_fiber(this);
|
||||
// `thread` is currently only used in `s_main`
|
||||
fcontext = _fl_jump_fcontext(fcontext, thread).fctx;
|
||||
finish_switch_fiber(this);
|
||||
}
|
||||
|
||||
inline void jmp_buf_link::switch_out() {
|
||||
g_current_context = link;
|
||||
auto from = _fl_jump_fcontext(link->fcontext, nullptr).fctx;
|
||||
link->fcontext = from;
|
||||
start_switch_fiber(link);
|
||||
link->fcontext = _fl_jump_fcontext(link->fcontext, thread).fctx;
|
||||
finish_switch_fiber(link);
|
||||
}
|
||||
|
||||
inline void jmp_buf_link::initial_switch_in_completed() {}
|
||||
inline void jmp_buf_link::initial_switch_in_completed() {
|
||||
#ifdef AS_INTERNAL_USE_ASAN
|
||||
// This is a new thread and it doesn't have the fake stack yet. ASan will
|
||||
// create it lazily, for now just pass nullptr.
|
||||
__sanitizer_finish_switch_fiber(nullptr, &link->asan_stack_bottom,
|
||||
&link->asan_stack_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
thread_context::thread_context(std::function<void()> func)
|
||||
: stack_size_(get_base_stack_size()), func_(std::move(func)) {
|
||||
inline void jmp_buf_link::final_switch_out() {
|
||||
g_current_context = link;
|
||||
#ifdef AS_INTERNAL_USE_ASAN
|
||||
// Since the thread is about to die we pass nullptr as fake_stack_save
|
||||
// argument so that ASan knows it can destroy the fake stack if it exists.
|
||||
__sanitizer_start_switch_fiber(nullptr, link->asan_stack_bottom,
|
||||
link->asan_stack_size);
|
||||
#endif
|
||||
_fl_jump_fcontext(link->fcontext, thread);
|
||||
|
||||
// never reach here
|
||||
assert(false);
|
||||
}
|
||||
|
||||
thread_context::thread_context(std::function<void()> func, size_t stack_size)
|
||||
: stack_size_(stack_size ? stack_size : get_base_stack_size()),
|
||||
func_(std::move(func)) {
|
||||
setup();
|
||||
}
|
||||
|
||||
|
@ -87,6 +142,10 @@ void thread_context::setup() {
|
|||
context_.fcontext = _fl_make_fcontext(stack_.get() + stack_size_,
|
||||
stack_size_, thread_context::s_main);
|
||||
context_.thread = this;
|
||||
#ifdef AS_INTERNAL_USE_ASAN
|
||||
context_.asan_stack_bottom = stack_.get();
|
||||
context_.asan_stack_size = stack_size_;
|
||||
#endif
|
||||
context_.switch_in();
|
||||
}
|
||||
|
||||
|
@ -96,6 +155,7 @@ void thread_context::switch_out() { context_.switch_out(); }
|
|||
|
||||
void thread_context::s_main(transfer_t t) {
|
||||
auto q = reinterpret_cast<thread_context*>(t.data);
|
||||
assert(g_current_context->thread == q);
|
||||
q->context_.link->fcontext = t.fctx;
|
||||
q->main();
|
||||
}
|
||||
|
@ -121,7 +181,7 @@ void thread_context::main() {
|
|||
done_.setException(std::current_exception());
|
||||
}
|
||||
|
||||
context_.switch_out();
|
||||
context_.final_switch_out();
|
||||
}
|
||||
|
||||
namespace thread_impl {
|
||||
|
|
|
@ -23,12 +23,11 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_INTERNAL_THREAD_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_INTERNAL_THREAD_H
|
||||
|
||||
#include <ucontext.h>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include <async_simple/Future.h>
|
||||
#include <async_simple/uthread/internal/thread_impl.h>
|
||||
#include "async_simple/Future.h"
|
||||
#include "async_simple/uthread/internal/thread_impl.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
@ -59,7 +58,7 @@ private:
|
|||
stack_holder make_stack();
|
||||
|
||||
public:
|
||||
explicit thread_context(std::function<void()> func);
|
||||
explicit thread_context(std::function<void()> func, size_t stack_size = 0);
|
||||
~thread_context();
|
||||
void switch_in();
|
||||
void switch_out();
|
||||
|
|
|
@ -23,8 +23,7 @@
|
|||
#ifndef ASYNC_SIMPLE_UTHREAD_INTERNAL_UTHREAD_IMPL_H
|
||||
#define ASYNC_SIMPLE_UTHREAD_INTERNAL_UTHREAD_IMPL_H
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <ucontext.h>
|
||||
#include "async_simple/Common.h"
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
@ -47,10 +46,17 @@ struct jmp_buf_link {
|
|||
jmp_buf_link* link = nullptr;
|
||||
thread_context* thread = nullptr;
|
||||
|
||||
#ifdef AS_INTERNAL_USE_ASAN
|
||||
const void* asan_stack_bottom = nullptr;
|
||||
std::size_t asan_stack_size = 0;
|
||||
void* asan_fake_stack = nullptr;
|
||||
#endif
|
||||
|
||||
public:
|
||||
void switch_in();
|
||||
void switch_out();
|
||||
void initial_switch_in_completed();
|
||||
void final_switch_out();
|
||||
};
|
||||
|
||||
extern thread_local jmp_buf_link* g_current_context;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
file(GLOB uthread_test_src "*.cpp")
|
||||
|
||||
# Compiler-rt15 enables new sanitizer option sanitize-address-use-after-return,
|
||||
# But it can't take user thread well. So disable it for thread.cc as a workaround.
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "13")
|
||||
set_property(SOURCE ${uthread_test_src} PROPERTY COMPILE_FLAGS -fsanitize-address-use-after-return=never)
|
||||
endif()
|
||||
|
||||
add_executable(async_simple_uthread_test ${uthread_test_src} ${PROJECT_SOURCE_DIR}/async_simple/test/dotest.cpp)
|
||||
|
||||
target_link_libraries(async_simple_uthread_test async_simple ${deplibs} ${testdeplibs})
|
||||
|
||||
add_test(NAME run_async_simple_uthread_test COMMAND async_simple_uthread_test)
|
||||
|
|
@ -1,541 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <async_simple/Common.h>
|
||||
#include <async_simple/coro/Lazy.h>
|
||||
#include <async_simple/executors/SimpleExecutor.h>
|
||||
#include <async_simple/test/unittest.h>
|
||||
#include <async_simple/uthread/Async.h>
|
||||
#include <async_simple/uthread/Await.h>
|
||||
#include <async_simple/uthread/Collect.h>
|
||||
#include <async_simple/uthread/Latch.h>
|
||||
#include <async_simple/uthread/Uthread.h>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
namespace uthread {
|
||||
|
||||
class UthreadTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
UthreadTest() : _executor(4) {}
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
|
||||
template <class Func>
|
||||
void delayedTask(Func&& func, std::size_t ms) {
|
||||
std::thread(
|
||||
[f = std::move(func), ms](Executor* ex) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
ex->schedule(std::move(f));
|
||||
},
|
||||
&_executor)
|
||||
.detach();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct Awaiter {
|
||||
Executor* ex;
|
||||
T value;
|
||||
|
||||
Awaiter(Executor* e, T v) : ex(e), value(v) {}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
void await_suspend(std::coroutine_handle<> handle) noexcept {
|
||||
auto ctx = ex->checkout();
|
||||
std::thread([handle, e = ex, ctx]() mutable {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
Executor::Func f = [handle]() mutable { handle.resume(); };
|
||||
e->checkin(std::move(f), ctx);
|
||||
}).detach();
|
||||
}
|
||||
T await_resume() noexcept { return value; }
|
||||
};
|
||||
template <class T>
|
||||
coro::Lazy<T> lazySum(T x, T y) {
|
||||
co_return co_await Awaiter{&_executor, x + y};
|
||||
}
|
||||
|
||||
protected:
|
||||
executors::SimpleExecutor _executor;
|
||||
};
|
||||
|
||||
TEST_F(UthreadTest, testSimple) {
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
std::atomic<bool> done(false);
|
||||
_executor.schedule([&done, ex = &_executor, &show]() {
|
||||
Uthread ut(Attribute{ex}, [&show]() { show("task 1"); });
|
||||
ut.join([ex, &done, &show]() {
|
||||
show("task 1 done");
|
||||
ex->schedule([ex, &done, &show]() {
|
||||
Uthread ut(Attribute{ex}, [&show]() { show("task 2"); });
|
||||
ut.join([&done, &show]() {
|
||||
show("task 2 done");
|
||||
done = true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
while (!done) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testSwitch) {
|
||||
Executor* ex = &_executor;
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
|
||||
auto ioJob = [&]() -> Future<int> {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(&_executor);
|
||||
delayedTask(
|
||||
[p = std::move(p)]() mutable {
|
||||
auto value = 1024;
|
||||
p.setValue(value);
|
||||
},
|
||||
100);
|
||||
return f;
|
||||
};
|
||||
|
||||
std::atomic<int> running = 2;
|
||||
_executor.schedule([ex, &running, &show, &ioJob]() mutable {
|
||||
Uthread task1(Attribute{ex}, [&running, &show, &ioJob]() {
|
||||
show("task1 start");
|
||||
auto value = await(ioJob());
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task1 done");
|
||||
running--;
|
||||
});
|
||||
task1.detach();
|
||||
});
|
||||
_executor.schedule([ex, &running, &show]() mutable {
|
||||
Uthread task2(Attribute{ex}, [&running, &show]() {
|
||||
show("task2 start");
|
||||
show("task2 done");
|
||||
running--;
|
||||
});
|
||||
task2.detach();
|
||||
});
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
class FakeExecutor : public executors::SimpleExecutor {
|
||||
public:
|
||||
FakeExecutor(size_t threadNum) : SimpleExecutor(threadNum) {}
|
||||
~FakeExecutor() {}
|
||||
|
||||
public:
|
||||
bool currentThreadInExecutor() const override { return true; }
|
||||
Context checkout() override { return NULLCTX; }
|
||||
bool checkin(Func func, Context ctx, ScheduleOptions opts) override {
|
||||
return schedule(std::move(func));
|
||||
}
|
||||
};
|
||||
|
||||
// reschedule uthread to two different executor is not thread-safe
|
||||
// this case used to check uthread's continuation switched in a new thread
|
||||
// successfully. detail see: jump_buf_link::switch_in
|
||||
TEST_F(UthreadTest, testScheduleInTwoThread) {
|
||||
auto ex = std::make_unique<executors::SimpleExecutor>(1);
|
||||
FakeExecutor fakeEx(1);
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << std::endl;
|
||||
};
|
||||
|
||||
auto ioJob = [&]() -> Future<int> {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(&fakeEx);
|
||||
delayedTask(
|
||||
[p = std::move(p), &ex]() mutable {
|
||||
auto value = 1024;
|
||||
// wait task done, avoid data race
|
||||
ex.reset();
|
||||
p.setValue(value);
|
||||
},
|
||||
1000);
|
||||
return f;
|
||||
};
|
||||
|
||||
std::atomic<int> running = 1;
|
||||
ex->schedule([ex = &fakeEx, &running, &show, &ioJob]() mutable {
|
||||
Uthread task(Attribute{ex}, [&running, &show, &ioJob]() {
|
||||
show("task start");
|
||||
auto value = await(ioJob());
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task done");
|
||||
running--;
|
||||
});
|
||||
task.detach();
|
||||
});
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testAsync) {
|
||||
Executor* ex = &_executor;
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
|
||||
auto ioJob = [&]() -> Future<int> {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(&_executor);
|
||||
delayedTask(
|
||||
[p = std::move(p)]() mutable {
|
||||
auto value = 1024;
|
||||
p.setValue(value);
|
||||
},
|
||||
100);
|
||||
return f;
|
||||
};
|
||||
|
||||
std::atomic<int> running = 2;
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, &ioJob]() {
|
||||
show("task1 start");
|
||||
auto value = await(ioJob());
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task1 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex]() {
|
||||
show("task2 start");
|
||||
async<Launch::Prompt>([&show]() { show("task3"); }, ex).detach();
|
||||
show("task2 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testAwait) {
|
||||
Executor* ex = &_executor;
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
|
||||
auto ioJob = [&](Promise<int> p) {
|
||||
delayedTask(
|
||||
[p = std::move(p)]() mutable {
|
||||
auto value = 1024;
|
||||
p.setValue(value);
|
||||
},
|
||||
100);
|
||||
};
|
||||
|
||||
std::atomic<int> running = 2;
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, &ioJob, ex]() {
|
||||
show("task1 start");
|
||||
auto value = await<int>(ex, ioJob);
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task1 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex]() {
|
||||
show("task2 start");
|
||||
async<Launch::Prompt>([&show]() { show("task3"); }, ex).detach();
|
||||
show("task2 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testAwaitCoroutine) {
|
||||
Executor* ex = &_executor;
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
|
||||
std::atomic<int> running = 2;
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex, this]() mutable {
|
||||
show("task1 start");
|
||||
auto value =
|
||||
await(ex, &std::remove_pointer_t<decltype(this)>::lazySum<int>,
|
||||
this, 1000, 24);
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task1 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex]() {
|
||||
show("task2 start");
|
||||
async<Launch::Prompt>([&show]() { show("task3"); }, ex).detach();
|
||||
show("task2 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace globalfn {
|
||||
|
||||
template <class T>
|
||||
struct Awaiter {
|
||||
Executor* ex;
|
||||
T value;
|
||||
|
||||
Awaiter(Executor* e, T v) : ex(e), value(v) {}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
void await_suspend(std::coroutine_handle<> handle) noexcept {
|
||||
auto ctx = ex->checkout();
|
||||
std::thread([handle, e = ex, ctx]() mutable {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
Executor::Func f = [handle]() mutable { handle.resume(); };
|
||||
e->checkin(std::move(f), ctx);
|
||||
}).detach();
|
||||
}
|
||||
T await_resume() noexcept { return value; }
|
||||
};
|
||||
template <class T>
|
||||
coro::Lazy<T> lazySum(Executor* ex, T x, T y) {
|
||||
co_return co_await Awaiter{ex, x + y};
|
||||
}
|
||||
|
||||
} // namespace globalfn
|
||||
|
||||
TEST_F(UthreadTest, testAwaitCoroutineNoneMemFn) {
|
||||
Executor* ex = &_executor;
|
||||
auto show = [&](const std::string& message) mutable {
|
||||
std::cout << message << "\n";
|
||||
};
|
||||
|
||||
std::atomic<int> running = 2;
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex]() mutable {
|
||||
show("task1 start");
|
||||
auto value = await(ex, globalfn::lazySum<int>, ex, 1000, 24);
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task1 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
auto lazySumWrapper = [ex = &_executor]() -> coro::Lazy<int> {
|
||||
co_return co_await globalfn::lazySum(ex, 1000, 24);
|
||||
};
|
||||
async<Launch::Schedule>(
|
||||
[&running, &show, ex, &lazySumWrapper]() mutable {
|
||||
show("task2 start");
|
||||
auto value = await(ex, lazySumWrapper);
|
||||
EXPECT_EQ(1024, value);
|
||||
show("task2 done");
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testCollectAllSimple) {
|
||||
static constexpr std::size_t kMaxTask = 10;
|
||||
|
||||
Executor* ex = &_executor;
|
||||
std::atomic<std::size_t> n = kMaxTask;
|
||||
std::vector<std::function<void()>> fs;
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
fs.emplace_back([i, &n]() {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(kMaxTask - i));
|
||||
n--;
|
||||
});
|
||||
}
|
||||
|
||||
std::atomic<int> running = 1;
|
||||
async<Launch::Schedule>(
|
||||
[&running, &n, ex, fs = std::move(fs)]() mutable {
|
||||
collectAll<Launch::Schedule>(fs.begin(), fs.end(), ex);
|
||||
EXPECT_EQ(0u, n);
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testCollectAllSlow) {
|
||||
static constexpr std::size_t kMaxTask = 10;
|
||||
Executor* ex = &_executor;
|
||||
|
||||
auto ioJob = [&](std::size_t delay_ms) -> Future<int> {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(ex);
|
||||
delayedTask(
|
||||
[p = std::move(p)]() mutable {
|
||||
auto value = 1024;
|
||||
p.setValue(value);
|
||||
},
|
||||
delay_ms);
|
||||
return f;
|
||||
};
|
||||
|
||||
std::vector<std::function<std::size_t()>> fs;
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
fs.emplace_back([i, &ioJob]() -> std::size_t {
|
||||
return i + await(ioJob(kMaxTask - i));
|
||||
});
|
||||
}
|
||||
|
||||
std::atomic<int> running = 1;
|
||||
async<Launch::Schedule>(
|
||||
[&running, ex, fs = std::move(fs)]() mutable {
|
||||
auto res = collectAll<Launch::Schedule>(fs.begin(), fs.end(), ex);
|
||||
EXPECT_EQ(kMaxTask, res.size());
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
EXPECT_EQ(i + 1024, res[i]);
|
||||
}
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testCollectAllSlowSingleThread) {
|
||||
static constexpr std::size_t kMaxTask = 10;
|
||||
Executor* ex = &_executor;
|
||||
|
||||
auto ioJob = [&](std::size_t delay_ms) -> Future<int> {
|
||||
Promise<int> p;
|
||||
auto f = p.getFuture().via(ex);
|
||||
delayedTask(
|
||||
[p = std::move(p)]() mutable {
|
||||
auto value = 1024;
|
||||
p.setValue(value);
|
||||
},
|
||||
delay_ms);
|
||||
return f;
|
||||
};
|
||||
|
||||
std::vector<std::function<std::size_t()>> fs;
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
fs.emplace_back([i, &ioJob]() -> std::size_t {
|
||||
return i + await(ioJob(kMaxTask - i));
|
||||
});
|
||||
}
|
||||
|
||||
std::atomic<int> running = 1;
|
||||
async<Launch::Schedule>(
|
||||
[&running, ex, fs = std::move(fs)]() mutable {
|
||||
auto res = collectAll<Launch::Current>(fs.begin(), fs.end(), ex);
|
||||
EXPECT_EQ(kMaxTask, res.size());
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
EXPECT_EQ(i + 1024, res[i]);
|
||||
}
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testLatch) {
|
||||
static constexpr std::size_t kMaxTask = 10;
|
||||
Executor* ex = &_executor;
|
||||
|
||||
Latch latch(kMaxTask);
|
||||
std::vector<std::function<void()>> fs;
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
fs.emplace_back([i, latchPtr = &latch]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(i));
|
||||
latchPtr->downCount();
|
||||
});
|
||||
}
|
||||
|
||||
std::atomic<int> running = 1;
|
||||
async<Launch::Schedule>(
|
||||
[&running, ex, fs = std::move(fs), latchPtr = &latch]() mutable {
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
async<Launch::Schedule>(std::move(fs[i]), ex);
|
||||
}
|
||||
latchPtr->await(ex);
|
||||
EXPECT_EQ(0u, latchPtr->currentCount());
|
||||
running--;
|
||||
},
|
||||
ex);
|
||||
|
||||
while (running) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(UthreadTest, testLatchThreadSafe) {
|
||||
static constexpr std::size_t kMaxTask = 1000;
|
||||
std::atomic<std::size_t> runningTask(kMaxTask);
|
||||
executors::SimpleExecutor taskEx(6);
|
||||
executors::SimpleExecutor taskNotify(8);
|
||||
|
||||
for (size_t i = 0; i < kMaxTask; ++i) {
|
||||
async<Launch::Schedule>(
|
||||
[&taskEx, &taskNotify, &runningTask]() mutable {
|
||||
auto f = [&taskEx, &taskNotify]() mutable {
|
||||
Latch latch(1u);
|
||||
taskNotify.schedule([latchPtr = &latch]() mutable {
|
||||
std::this_thread::sleep_for(1us);
|
||||
latchPtr->downCount();
|
||||
});
|
||||
latch.await(&taskEx);
|
||||
};
|
||||
std::vector<std::function<void()>> fvec;
|
||||
fvec.emplace_back(f);
|
||||
fvec.emplace_back(f);
|
||||
fvec.emplace_back(f);
|
||||
collectAll<Launch::Schedule>(fvec.begin(), fvec.end(), &taskEx);
|
||||
std::vector<std::function<void()>> fvec2;
|
||||
fvec2.emplace_back(f);
|
||||
fvec2.emplace_back(f);
|
||||
fvec2.emplace_back(f);
|
||||
collectAll<Launch::Current>(fvec2.begin(), fvec2.end(),
|
||||
&taskEx);
|
||||
|
||||
runningTask.fetch_sub(1u);
|
||||
},
|
||||
&taskEx);
|
||||
std::this_thread::sleep_for(10us);
|
||||
}
|
||||
|
||||
while (runningTask) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uthread
|
||||
} // namespace async_simple
|
|
@ -19,13 +19,25 @@
|
|||
#ifndef ASYNC_SIMPLE_UTIL_CONDITION_H
|
||||
#define ASYNC_SIMPLE_UTIL_CONDITION_H
|
||||
|
||||
#if __has_include(<semaphore>)
|
||||
#include <semaphore>
|
||||
#else
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
namespace async_simple {
|
||||
namespace util {
|
||||
|
||||
#if __has_include(<semaphore>)
|
||||
class Condition : public std::binary_semaphore {
|
||||
public:
|
||||
explicit Condition(ptrdiff_t num = 0) : std::binary_semaphore(num) {}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class Condition {
|
||||
public:
|
||||
void release() {
|
||||
|
@ -46,6 +58,8 @@ private:
|
|||
size_t _count = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace util
|
||||
} // namespace async_simple
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <async_simple/util/Queue.h>
|
||||
#include "async_simple/util/Queue.h"
|
||||
namespace async_simple::util {
|
||||
class ThreadPool {
|
||||
public:
|
||||
|
@ -97,7 +97,7 @@ inline ThreadPool::ThreadPool(size_t threadNum, bool enableWorkSteal,
|
|||
auto current = getCurrent();
|
||||
current->first = id;
|
||||
current->second = this;
|
||||
while (!_stop) {
|
||||
while (true) {
|
||||
WorkItem workerItem = {};
|
||||
if (_enableWorkSteal) {
|
||||
// Try to do work steal firstly.
|
||||
|
@ -109,10 +109,15 @@ inline ThreadPool::ThreadPool(size_t threadNum, bool enableWorkSteal,
|
|||
}
|
||||
}
|
||||
|
||||
// If _enableWorkSteal false or work steal failed, wait for a pop
|
||||
// task.
|
||||
if (!workerItem.fn && !_queues[id].pop(workerItem))
|
||||
continue;
|
||||
if (!workerItem.fn && !_queues[id].pop(workerItem)) {
|
||||
// If thread is going to stop, don't wait for any new task any
|
||||
// more. Otherwise wait for a pop task if _enableWorkSteal false
|
||||
// or work steal failed,
|
||||
if (_stop)
|
||||
break;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
if (workerItem.fn)
|
||||
workerItem.fn();
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
file(GLOB util_test_src "*.cpp")
|
||||
add_executable(async_simple_util_test ${util_test_src} ${PROJECT_SOURCE_DIR}/async_simple/test/dotest.cpp)
|
||||
|
||||
target_link_libraries(async_simple_util_test async_simple ${deplibs} ${testdeplibs})
|
||||
|
||||
add_test(NAME run_async_simple_util_test COMMAND async_simple_util_test)
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Alibaba Group Holding Limited;
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <gtest/gtest.h>
|
||||
#include <exception>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "async_simple/test/unittest.h"
|
||||
#include "async_simple/util/ThreadPool.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace async_simple {
|
||||
|
||||
class ThreadPoolTest : public FUTURE_TESTBASE {
|
||||
public:
|
||||
shared_ptr<async_simple::util::ThreadPool> _tp;
|
||||
|
||||
public:
|
||||
void caseSetUp() override {}
|
||||
void caseTearDown() override {}
|
||||
};
|
||||
|
||||
TEST_F(ThreadPoolTest, testScheduleWithId) {
|
||||
_tp = make_shared<async_simple::util::ThreadPool>(2);
|
||||
std::thread::id id1, id2, id3;
|
||||
std::atomic<bool> done1(false), done2(false), done3(false), done4(false);
|
||||
std::function<void()> f1 = [this, &done1, &id1]() {
|
||||
id1 = std::this_thread::get_id();
|
||||
ASSERT_EQ(_tp->getCurrentId(), 0);
|
||||
done1 = true;
|
||||
};
|
||||
std::function<void()> f2 = [this, &done2, &id2]() {
|
||||
id2 = std::this_thread::get_id();
|
||||
ASSERT_EQ(_tp->getCurrentId(), 0);
|
||||
done2 = true;
|
||||
};
|
||||
std::function<void()> f3 = [this, &done3, &id3]() {
|
||||
id3 = std::this_thread::get_id();
|
||||
ASSERT_EQ(_tp->getCurrentId(), 1);
|
||||
done3 = true;
|
||||
};
|
||||
std::function<void()> f4 = [&done4]() { done4 = true; };
|
||||
_tp->scheduleById(std::move(f1), 0);
|
||||
_tp->scheduleById(std::move(f2), 0);
|
||||
_tp->scheduleById(std::move(f3), 1);
|
||||
_tp->scheduleById(std::move(f4));
|
||||
|
||||
while (!done1.load() || !done2.load() || !done3.load() || !done4.load())
|
||||
;
|
||||
ASSERT_TRUE(id1 == id2) << id1 << " " << id2;
|
||||
ASSERT_TRUE(id1 != id3) << id1 << " " << id3;
|
||||
ASSERT_TRUE(_tp->getCurrentId() == -1);
|
||||
}
|
||||
|
||||
using namespace async_simple::util;
|
||||
|
||||
void TestBasic(ThreadPool& pool) {
|
||||
EXPECT_EQ(ThreadPool::ERROR_TYPE::ERROR_NONE, pool.scheduleById([] {}));
|
||||
EXPECT_GE(pool.getItemCount(), 0u);
|
||||
|
||||
EXPECT_EQ(ThreadPool::ERROR_TYPE::ERROR_POOL_ITEM_IS_NULL,
|
||||
pool.scheduleById(nullptr));
|
||||
EXPECT_EQ(pool.getCurrentId(), -1);
|
||||
|
||||
pool.scheduleById([&pool] { EXPECT_EQ(pool.getCurrentId(), 1); }, 1);
|
||||
}
|
||||
|
||||
TEST(ThreadTest, BasicTest) {
|
||||
ThreadPool pool;
|
||||
EXPECT_EQ(std::thread::hardware_concurrency(),
|
||||
static_cast<decltype(std::thread::hardware_concurrency())>(
|
||||
pool.getThreadNum()));
|
||||
ThreadPool pool1(2);
|
||||
EXPECT_EQ(2, pool1.getThreadNum());
|
||||
|
||||
TestBasic(pool);
|
||||
|
||||
ThreadPool tp(std::thread::hardware_concurrency(),
|
||||
/*enableWorkSteal = */ true);
|
||||
TestBasic(tp);
|
||||
}
|
||||
|
||||
} // namespace async_simple
|
Loading…
Reference in New Issue