Update async simple (#282)

* [third_party] Update async_simple

---------

Co-authored-by: PikachuHy <pikachuhy@linux.alibaba.com>
This commit is contained in:
saipubw 2023-05-13 20:27:54 +08:00 committed by GitHub
parent a25474a63a
commit 924b07bdc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 4172 additions and 4849 deletions

3
.gitignore vendored
View File

@ -52,4 +52,5 @@ node_modules
# Bazel
bazel-*
!BUILD.bazel
!BUILD.bazel
cdb.json

101
thirdparty/async_simple/.clang-format vendored Normal file
View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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 ...

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ...

34
thirdparty/async_simple/.gitignore vendored Normal file
View File

@ -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

View File

@ -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)

View File

@ -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);
});
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
};

View File

@ -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 {

View File

@ -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());
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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
* generators range and iterators
* reference specifies the reference type (not necessarily a core language
* reference type) of the generators 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 generators 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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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>

View File

@ -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));

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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));
}

View File

@ -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

View File

@ -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 {

View File

@ -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;

View 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

View 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

View 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

View 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

View 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

View File

@ -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

View 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

View 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

View 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

View File

@ -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 */

View File

@ -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 */

View File

@ -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 {

View File

@ -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();

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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)

View File

@ -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