├── .clang-format ├── .github ├── actions │ ├── cmake-build │ │ └── action.yml │ ├── fetch-clang │ │ └── action.yml │ ├── fetch-cmake │ │ └── action.yml │ ├── fetch-gcc │ │ └── action.yml │ ├── fetch-libstdc++ │ │ └── action.yml │ ├── fetch-ninja │ │ └── action.yml │ └── run-tests │ │ └── action.yml └── workflows │ ├── ci.yml │ └── test_matrix.json ├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── cmake ├── concurrencppConfig.cmake ├── concurrencppInjectTSAN.cmake ├── coroutineOptions.cmake └── libc++.cmake ├── example ├── 10_regular_timer │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 11_oneshot_timer │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 12_delay_object │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 13_generator │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 1_hello_world │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 2_concurrent_even_number_counting │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 3_async_file_processing │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 4_async_file_processing_version_2 │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 5_prime_number_finder │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 6_manual_executor │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 7_when_all │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 8_when_any │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp ├── 9_result_promise │ ├── CMakeLists.txt │ └── source │ │ └── main.cpp └── CMakeLists.txt ├── include └── concurrencpp │ ├── concurrencpp.h │ ├── coroutines │ └── coroutine.h │ ├── errors.h │ ├── executors │ ├── constants.h │ ├── derivable_executor.h │ ├── executor.h │ ├── executor_all.h │ ├── inline_executor.h │ ├── manual_executor.h │ ├── thread_executor.h │ ├── thread_pool_executor.h │ └── worker_thread_executor.h │ ├── forward_declarations.h │ ├── platform_defs.h │ ├── results │ ├── constants.h │ ├── generator.h │ ├── impl │ │ ├── consumer_context.h │ │ ├── generator_state.h │ │ ├── lazy_result_state.h │ │ ├── producer_context.h │ │ ├── result_state.h │ │ ├── return_value_struct.h │ │ └── shared_result_state.h │ ├── lazy_result.h │ ├── lazy_result_awaitable.h │ ├── make_result.h │ ├── promises.h │ ├── result.h │ ├── result_awaitable.h │ ├── result_fwd_declarations.h │ ├── resume_on.h │ ├── shared_result.h │ ├── shared_result_awaitable.h │ └── when_result.h │ ├── runtime │ ├── constants.h │ └── runtime.h │ ├── task.h │ ├── threads │ ├── async_condition_variable.h │ ├── async_lock.h │ ├── cache_line.h │ ├── constants.h │ └── thread.h │ ├── timers │ ├── constants.h │ ├── timer.h │ └── timer_queue.h │ └── utils │ ├── bind.h │ └── slist.h ├── sandbox ├── CMakeLists.txt └── main.cpp ├── source ├── executors │ ├── executor.cpp │ ├── manual_executor.cpp │ ├── thread_executor.cpp │ ├── thread_pool_executor.cpp │ └── worker_thread_executor.cpp ├── results │ └── impl │ │ ├── consumer_context.cpp │ │ ├── result_state.cpp │ │ └── shared_result_state.cpp ├── runtime │ └── runtime.cpp ├── task.cpp ├── threads │ ├── async_condition_variable.cpp │ ├── async_lock.cpp │ └── thread.cpp └── timers │ ├── timer.cpp │ └── timer_queue.cpp └── test ├── CMakeLists.txt ├── include ├── infra │ ├── assertions.h │ └── tester.h └── utils │ ├── custom_exception.h │ ├── executor_shutdowner.h │ ├── object_observer.h │ ├── random.h │ ├── test_generators.h │ ├── test_ready_lazy_result.h │ ├── test_ready_result.h │ ├── test_thread_callbacks.h │ └── throwing_executor.h └── source ├── infra ├── assertions.cpp └── tester.cpp ├── tests ├── async_condition_variable_tests.cpp ├── async_lock_tests.cpp ├── coroutine_tests │ ├── coroutine_promise_tests.cpp │ └── coroutine_tests.cpp ├── executor_tests │ ├── inline_executor_tests.cpp │ ├── manual_executor_tests.cpp │ ├── thread_executor_tests.cpp │ ├── thread_pool_executor_tests.cpp │ └── worker_thread_executor_tests.cpp ├── result_tests │ ├── generator_tests.cpp │ ├── lazy_result_tests.cpp │ ├── make_result_tests.cpp │ ├── result_promise_tests.cpp │ ├── result_resolve_await_tests.cpp │ ├── result_tests.cpp │ ├── resume_on_tests.cpp │ ├── shared_result_await_tests.cpp │ ├── shared_result_resolve_tests.cpp │ ├── shared_result_tests.cpp │ ├── when_all_tests.cpp │ └── when_any_tests.cpp ├── runtime_tests.cpp ├── scoped_async_lock_tests.cpp ├── task_tests.cpp └── timer_tests │ ├── timer_queue_tests.cpp │ └── timer_tests.cpp ├── thread_sanitizer ├── async_condition_variable.cpp ├── async_lock.cpp ├── executors.cpp ├── fibonacci.cpp ├── lazy_fibonacci.cpp ├── matrix_multiplication.cpp ├── quick_sort.cpp ├── result.cpp ├── shared_result.cpp ├── when_all.cpp └── when_any.cpp └── utils └── object_observer.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: 'false' 5 | AlignConsecutiveDeclarations: 'false' 6 | AlignOperands: 'false' 7 | AlignTrailingComments: 'false' 8 | AllowAllArgumentsOnNextLine: 'false' 9 | AllowAllConstructorInitializersOnNextLine: 'true' 10 | AllowAllParametersOfDeclarationOnNextLine: 'false' 11 | AllowShortBlocksOnASingleLine: 'false' 12 | AllowShortCaseLabelsOnASingleLine: 'false' 13 | AllowShortFunctionsOnASingleLine: Empty 14 | AllowShortIfStatementsOnASingleLine: Never 15 | AllowShortLambdasOnASingleLine: None 16 | AllowShortLoopsOnASingleLine: 'false' 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: 'false' 20 | AlwaysBreakTemplateDeclarations: 'Yes' 21 | BreakBeforeBinaryOperators: None 22 | BreakBeforeBraces: Attach 23 | BreakBeforeTernaryOperators: 'false' 24 | BreakConstructorInitializers: AfterColon 25 | BreakInheritanceList: AfterColon 26 | BreakStringLiterals: 'false' 27 | CompactNamespaces: 'false' 28 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 29 | Cpp11BracedListStyle: 'true' 30 | IncludeBlocks: Preserve 31 | IndentCaseLabels: 'true' 32 | IndentPPDirectives: AfterHash 33 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 34 | Language: Cpp 35 | PointerAlignment: Left 36 | SortIncludes: 'false' 37 | SortUsingDeclarations: 'false' 38 | SpaceAfterCStyleCast: 'false' 39 | SpaceAfterLogicalNot: 'false' 40 | SpaceAfterTemplateKeyword: 'false' 41 | SpaceBeforeAssignmentOperators: 'true' 42 | SpaceBeforeCpp11BracedList: 'true' 43 | SpaceBeforeCtorInitializerColon: 'true' 44 | SpaceBeforeInheritanceColon: 'true' 45 | SpaceBeforeParens: ControlStatements 46 | SpaceBeforeRangeBasedForLoopColon: 'true' 47 | SpaceInEmptyParentheses: 'false' 48 | SpacesInAngles: 'false' 49 | SpacesInCStyleCastParentheses: 'false' 50 | SpacesInContainerLiterals: 'true' 51 | SpacesInParentheses: 'false' 52 | SpacesInSquareBrackets: 'false' 53 | Standard: Cpp11 54 | ColumnLimit: 135 55 | IndentWidth: 4 56 | BinPackArguments: 'false' 57 | BinPackParameters: 'false' 58 | AllowAllArgumentsOnNextLine: 'false' 59 | NamespaceIndentation: All 60 | ... 61 | -------------------------------------------------------------------------------- /.github/actions/cmake-build/action.yml: -------------------------------------------------------------------------------- 1 | name: CMake Build 2 | description: Build CMake Project 3 | 4 | inputs: 5 | cmake: 6 | description: Path to CMake executable 7 | required: True 8 | ninja: 9 | description: Path to ninja executable 10 | required: True 11 | source: 12 | description: Path to source directory 13 | required: True 14 | build: 15 | description: Path to build directory 16 | required: True 17 | jobs: 18 | description: Number of jobs to use 19 | default: 1 20 | config: 21 | description: CMake configuration to build 22 | default: RelWithDebInfo 23 | args: 24 | description: Extra arguments to pass CMake 25 | 26 | runs: 27 | using: composite 28 | steps: 29 | - shell: pwsh 30 | run: | 31 | function Invoke-NativeCommand { 32 | $command = $args[0] 33 | $arguments = $args[1..($args.Length)] 34 | & $command @arguments 35 | if ($LastExitCode -ne 0) { 36 | Write-Error "Exit code $LastExitCode while running $command $arguments" 37 | } 38 | } 39 | if ($IsWindows) { 40 | $vsPath = &"${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -Property InstallationPath 41 | Import-Module (Get-ChildItem $vsPath -Recurse -File -Filter Microsoft.VisualStudio.DevShell.dll).FullName 42 | Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation -DevCmdArguments '-arch=x64' 43 | } 44 | Invoke-NativeCommand '${{ inputs.cmake }}' '-S${{ inputs.source }}' '-B${{ inputs.build }}' '-GNinja Multi-Config' '-DCMAKE_MAKE_PROGRAM=${{ inputs.ninja }}' '-DCMAKE_INSTALL_PREFIX=${{ inputs.build }}/prefix' ${{ inputs.args }} 45 | Invoke-NativeCommand '${{ inputs.cmake }}' --build '${{ inputs.build }}' --config '${{ inputs.config }}' -j${{ inputs.jobs }} '--' -k0 46 | -------------------------------------------------------------------------------- /.github/actions/fetch-clang/action.yml: -------------------------------------------------------------------------------- 1 | name: Fetch Clang 2 | description: Puts clang's path into the output 3 | 4 | inputs: 5 | version: 6 | description: Version of Clang to fetch 7 | required: true 8 | base-directory: 9 | description: Directory in which to install clang 10 | outputs: 11 | clang: 12 | description: Path of clang executable 13 | value: ${{ steps.script.outputs.clang }} 14 | clangxx: 15 | description: Path of clang++ executable 16 | value: ${{ steps.script.outputs.clangxx }} 17 | 18 | runs: 19 | using: composite 20 | steps: 21 | - id: script 22 | shell: pwsh 23 | working-directory: ${{ inputs.base-directory }} 24 | run: | 25 | $version = ${{ inputs.version }} 26 | function Invoke-NativeCommand { 27 | $command = $args[0] 28 | $arguments = $args[1..($args.Length)] 29 | & $command @arguments 30 | if ($LastExitCode -ne 0) { 31 | Write-Error "Exit code $LastExitCode while running $command $arguments" 32 | } 33 | } 34 | if ($IsMacOs) { 35 | } elseif ($IsLinux) { 36 | $tmp = New-TemporaryFile 37 | Invoke-WebRequest -Uri 'https://apt.llvm.org/llvm-snapshot.gpg.key' -OutFile $tmp 38 | Invoke-NativeCommand sudo apt-key add $tmp 39 | $tmp | Remove-Item 40 | Invoke-NativeCommand sudo add-apt-repository -y "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${version} main" 41 | Invoke-NativeCommand sudo apt-get update 42 | $pkgs = @("clang-${version}", "libc++-${version}-dev", "libc++abi-${version}-dev") 43 | if (${version} -eq 12) { 44 | $pkgs += "libunwind-${version}-dev" 45 | } 46 | Invoke-NativeCommand sudo apt-get install -y $pkgs 47 | Add-Content "${env:GITHUB_OUTPUT}" "clang=$((Get-Command clang-${version}).Source)" 48 | Add-Content "${env:GITHUB_OUTPUT}" "clangxx=$((Get-Command clang++-${version}).Source)" 49 | } elseif ($IsWindows) { 50 | $release = Invoke-WebRequest -Uri 'https://api.github.com/repos/llvm/llvm-project/releases' -UseBasicParsing | 51 | ConvertFrom-Json | 52 | Select-Object -Property @{Name = 'version'; Expression = {[System.Management.Automation.SemanticVersion]$_.tag_name.Substring('llvmorg-'.Length)}},assets | 53 | Where-Object {$_.version.Major -eq $version -and ($_.assets | Where-Object {$_.name -like "LLVM-*-win64.exe"})} | 54 | Sort-Object | 55 | Select-Object -First 1 56 | $uri = ($release.assets | Where-Object {$_.name -eq "LLVM-$($release.version)-win64.exe"}).browser_download_url 57 | $tmp = New-TemporaryFile | Rename-Item -NewName { $_ -replace 'tmp$', 'exe' } –PassThru 58 | Invoke-WebRequest -Uri $uri -OutFile $tmp 59 | Start-Process "$tmp" -Wait -NoNewWindow -ArgumentList /S,"/D=$(Join-Path (Get-Location) LLVM)" 60 | $tmp | Remove-Item 61 | Add-Content "${env:GITHUB_OUTPUT}" "clang=$(Join-Path (Get-Location) LLVM bin clang)" 62 | Add-Content "${env:GITHUB_OUTPUT}" "clangxx=$(Join-Path (Get-Location) LLVM bin clang++)" 63 | } 64 | -------------------------------------------------------------------------------- /.github/actions/fetch-cmake/action.yml: -------------------------------------------------------------------------------- 1 | name: Fetch CMake 2 | description: Puts CMake's path into the output 3 | 4 | inputs: 5 | version: 6 | description: Version of CMake to fetch 7 | default: 3.24.2 8 | base-directory: 9 | description: Directory in which to install CMake 10 | outputs: 11 | cmake: 12 | description: Path of CMake executable 13 | value: ${{ steps.script.outputs.cmake }} 14 | ctest: 15 | description: Path of CTest executable 16 | value: ${{ steps.script.outputs.ctest }} 17 | 18 | runs: 19 | using: composite 20 | steps: 21 | - id: script 22 | shell: pwsh 23 | working-directory: ${{ inputs.base-directory }} 24 | run: | 25 | $version = '${{ inputs.version }}' 26 | $oldVersion = [System.Version]$version -lt [System.Version]'3.20.0' 27 | $arch = 'x86_64' 28 | $ext = 'tar.gz' 29 | $binDir = 'bin' 30 | if ($IsMacOs) { 31 | if ($oldVersion) { 32 | $os = 'Darwin' 33 | } else { 34 | $os = 'macos' 35 | $arch = 'universal' 36 | } 37 | $binDir = 'CMake.app/Contents/bin' 38 | } elseif ($IsLinux) { 39 | if ($oldVersion) { 40 | $os = 'Linux' 41 | } else { 42 | $os = 'linux' 43 | } 44 | } elseif ($IsWindows) { 45 | if ($oldVersion) { 46 | $os = 'win64' 47 | $arch = 'x64' 48 | } else { 49 | $os = 'windows' 50 | } 51 | $ext = 'zip' 52 | } 53 | $base = "cmake-${version}-${os}-${arch}" 54 | $uri = "https://github.com/Kitware/CMake/releases/download/v${version}/${base}.${ext}" 55 | $tmp = New-TemporaryFile 56 | Invoke-WebRequest -Uri $uri -OutFile $tmp 57 | cmake -E tar xf $tmp 58 | $tmp | Remove-Item 59 | Add-Content "${env:GITHUB_OUTPUT}" "cmake=$(Join-Path (Get-Location) $base $binDir cmake)" 60 | Add-Content "${env:GITHUB_OUTPUT}" "ctest=$(Join-Path (Get-Location) $base $binDir ctest)" 61 | -------------------------------------------------------------------------------- /.github/actions/fetch-gcc/action.yml: -------------------------------------------------------------------------------- 1 | name: Fetch GCC 2 | description: Puts gcc's path into the output 3 | 4 | inputs: 5 | version: 6 | description: Version of GCC to fetch 7 | required: true 8 | outputs: 9 | gcc: 10 | description: Path of gcc executable 11 | value: ${{ steps.script.outputs.gcc }} 12 | gplusplus: 13 | description: Path of g++ executable 14 | value: ${{ steps.script.outputs.gplusplus }} 15 | 16 | runs: 17 | using: composite 18 | steps: 19 | - id: script 20 | shell: pwsh 21 | run: | 22 | $version = "${{ inputs.version }}" 23 | function Invoke-NativeCommand { 24 | $command = $args[0] 25 | $arguments = $args[1..($args.Length)] 26 | & $command @arguments 27 | if ($LastExitCode -ne 0) { 28 | Write-Error "Exit code $LastExitCode while running $command $arguments" 29 | } 30 | } 31 | if ($IsMacOs) { 32 | Invoke-NativeCommand brew install gcc@${version} 33 | Add-Content "${env:GITHUB_OUTPUT}" "gcc=gcc-${version}" 34 | Add-Content "${env:GITHUB_OUTPUT}" "gplusplus=g++-${version}" 35 | } elseif ($IsLinux) { 36 | # install Homebrew 37 | $env:CI = "1" 38 | Invoke-NativeCommand /bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh) 39 | $env:CI = $null 40 | # install gcc 41 | Invoke-NativeCommand /home/linuxbrew/.linuxbrew/bin/brew install gcc@${version} 42 | Add-Content "${env:GITHUB_OUTPUT}" "gcc=/home/linuxbrew/.linuxbrew/bin/gcc-${version}" 43 | Add-Content "${env:GITHUB_OUTPUT}" "gplusplus=/home/linuxbrew/.linuxbrew/bin/g++-${version}" 44 | } elseif ($IsWindows) { 45 | Write-Error "GCC installation on Windows is not supported" 46 | } 47 | -------------------------------------------------------------------------------- /.github/actions/fetch-libstdc++/action.yml: -------------------------------------------------------------------------------- 1 | name: Fetch libstdc++ 2 | description: Fetches libstdc++ 3 | 4 | inputs: 5 | version: 6 | description: Version of libstdc++ to fetch 7 | required: true 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - shell: pwsh 13 | run: | 14 | function Invoke-NativeCommand { 15 | $command = $args[0] 16 | $arguments = $args[1..($args.Length)] 17 | & $command @arguments 18 | if ($LastExitCode -ne 0) { 19 | Write-Error "Exit code $LastExitCode while running $command $arguments" 20 | } 21 | } 22 | Invoke-NativeCommand sudo apt-get update 23 | Invoke-NativeCommand sudo apt-get install -y libstdc++-${{ inputs.version }}-dev 24 | -------------------------------------------------------------------------------- /.github/actions/fetch-ninja/action.yml: -------------------------------------------------------------------------------- 1 | name: Fetch Ninja 2 | description: Puts ninja's path into the output 3 | 4 | inputs: 5 | version: 6 | description: Version of Ninja to fetch 7 | default: 1.11.1 8 | base-directory: 9 | description: Directory in which to install Ninja 10 | outputs: 11 | ninja: 12 | description: Path of ninja executable 13 | value: ${{ steps.script.outputs.ninja }} 14 | 15 | runs: 16 | using: composite 17 | steps: 18 | - id: script 19 | shell: pwsh 20 | working-directory: ${{ inputs.base-directory }} 21 | run: | 22 | $version = '${{ inputs.version }}' 23 | if ($IsMacOs) { 24 | $os = 'mac' 25 | } elseif ($IsLinux) { 26 | $os = 'linux' 27 | } elseif ($IsWindows) { 28 | $os = 'win' 29 | } 30 | $uri = "https://github.com/ninja-build/ninja/releases/download/v${version}/ninja-${os}.zip" 31 | $tmp = New-TemporaryFile 32 | Invoke-WebRequest -Uri $uri -OutFile $tmp 33 | cmake -E tar xf $tmp 34 | $tmp | Remove-Item 35 | Add-Content "${env:GITHUB_OUTPUT}" "ninja=$(Join-Path (Get-Location) ninja)" 36 | -------------------------------------------------------------------------------- /.github/actions/run-tests/action.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | description: Run Tests 3 | 4 | inputs: 5 | ctest: 6 | description: Path to CTest executable 7 | required: True 8 | test-dir: 9 | description: Path to test directory 10 | required: True 11 | attempts: 12 | description: Number of attempts to run per test 13 | default: 3 14 | jobs: 15 | description: Number of jobs to use 16 | default: 1 17 | config: 18 | description: CTest configuration to test 19 | default: RelWithDebInfo 20 | 21 | runs: 22 | using: composite 23 | steps: 24 | - shell: pwsh 25 | run: | 26 | & '${{ inputs.ctest }}' --test-dir '${{ inputs.test-dir }}' -C ${{ inputs.config }} -V -j${{ inputs.jobs }} --repeat until-pass:${{ inputs.attempts }} 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | 9 | pull_request: 10 | branches: 11 | - master 12 | - develop 13 | 14 | workflow_dispatch: ~ 15 | 16 | env: 17 | CTEST_OUTPUT_ON_FAILURE: 1 18 | NINJA_STATUS: '[%f/%t %o/sec] ' 19 | 20 | jobs: 21 | build-matrix: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | tests-matrix: ${{ steps.script.outputs.matrix }} 25 | steps: 26 | - uses: actions/checkout@v2 27 | - id: script 28 | shell: pwsh 29 | run: | 30 | $json = Get-Content -Raw .github/workflows/test_matrix.json | ConvertFrom-Json 31 | Add-Content "${env:GITHUB_OUTPUT}" "matrix=$(ConvertTo-Json $json -Compress)" 32 | tests: 33 | needs: build-matrix 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | include: 38 | ${{ fromJSON(needs.build-matrix.outputs.tests-matrix) }} 39 | name: ${{ matrix.name }} 40 | runs-on: ${{ matrix.os }} 41 | steps: 42 | - uses: actions/checkout@v3 43 | 44 | - uses: friendlyanon/fetch-core-count@v1 45 | id: cores 46 | 47 | - shell: pwsh 48 | run: New-Item build/tools -ItemType Directory -ErrorAction SilentlyContinue 49 | 50 | - shell: sudo pwsh -File {0} 51 | run: | 52 | Add-Content -Value 'Acquire::Retries "100";' -Path /etc/apt/apt.conf.d/99-custom 53 | Add-Content -Value 'Acquire::https::Timeout "240";' -Path /etc/apt/apt.conf.d/99-custom 54 | Add-Content -Value 'Acquire::http::Timeout "240";' -Path /etc/apt/apt.conf.d/99-custom 55 | Add-Content -Value 'APT::Get::Assume-Yes "true";' -Path /etc/apt/apt.conf.d/99-custom 56 | Add-Content -Value 'APT::Install-Recommends "false";' -Path /etc/apt/apt.conf.d/99-custom 57 | Add-Content -Value 'APT::Install-Suggests "false";' -Path /etc/apt/apt.conf.d/99-custom 58 | if: ${{ startsWith(matrix.os, 'ubuntu') }} 59 | 60 | - uses: ./.github/actions/fetch-cmake 61 | id: cmake 62 | with: 63 | base-directory: build/tools 64 | 65 | - uses: ./.github/actions/fetch-ninja 66 | id: ninja 67 | with: 68 | base-directory: build/tools 69 | 70 | - uses: ./.github/actions/fetch-libstdc++ 71 | id: libstdcxx 72 | with: 73 | version: ${{ matrix.libstdcxx-version }} 74 | if: ${{ matrix.stdlib == 'libstdc++' && matrix.libstdcxx-version }} 75 | 76 | - uses: ./.github/actions/fetch-clang 77 | id: clang 78 | with: 79 | version: ${{ matrix.clang-version }} 80 | base-directory: build/tools 81 | if: ${{ matrix.clang-version }} 82 | 83 | - uses: ./.github/actions/fetch-gcc 84 | id: gcc 85 | with: 86 | version: ${{ matrix.gcc-version }} 87 | if: ${{ matrix.gcc-version }} 88 | 89 | - name: Build Examples 90 | uses: ./.github/actions/cmake-build 91 | continue-on-error: ${{ matrix.os == 'macos-11' }} 92 | env: 93 | CXX: ${{ steps.clang.outputs.clangxx || steps.gcc.outputs.gplusplus }} 94 | with: 95 | cmake: ${{ steps.cmake.outputs.cmake }} 96 | ninja: ${{ steps.ninja.outputs.ninja }} 97 | jobs: ${{ steps.cores.outputs.plus_one }} 98 | source: example 99 | build: build/example 100 | args: > 101 | -DBUILD_SHARED_LIBS=${{ matrix.shared }} 102 | ${{ ( matrix.stdlib == 'libc++' && '-DCMAKE_CXX_FLAGS=-stdlib=libc++' ) || '' }} 103 | 104 | - name: Build Tests 105 | id: build_tests 106 | uses: ./.github/actions/cmake-build 107 | continue-on-error: ${{ matrix.os == 'macos-11' }} 108 | env: 109 | CXX: ${{ steps.clang.outputs.clangxx || steps.gcc.outputs.gplusplus }} 110 | with: 111 | cmake: ${{ steps.cmake.outputs.cmake }} 112 | ninja: ${{ steps.ninja.outputs.ninja }} 113 | jobs: ${{ steps.cores.outputs.plus_one }} 114 | source: test 115 | build: build/test 116 | args: > 117 | -DBUILD_SHARED_LIBS=${{ matrix.shared }} 118 | -DENABLE_THREAD_SANITIZER=${{ matrix.tsan }} 119 | ${{ ( matrix.stdlib == 'libc++' && '-DCMAKE_CXX_FLAGS=-stdlib=libc++' ) || '' }} 120 | 121 | - uses: ./.github/actions/run-tests 122 | continue-on-error: ${{ startsWith(matrix.os, 'macos') }} 123 | if: steps.build_tests.outcome == 'success' 124 | with: 125 | ctest: ${{ steps.cmake.outputs.ctest }} 126 | jobs: ${{ steps.cores.outputs.plus_one }} 127 | test-dir: build/test 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/c++ 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=c++ 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | # End of https://www.toptal.com/developers/gitignore/api/c++ 39 | 40 | # Others 41 | .vs 42 | CMakeSettings.json 43 | 44 | # Specific directories 45 | build/ 46 | msvc_intermediate/ 47 | out/ 48 | 49 | # CLion 50 | .idea/ 51 | **/cmake-build-*/ 52 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 David Haim 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cmake/concurrencppConfig.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | find_dependency(Threads) 3 | 4 | include("${CMAKE_CURRENT_LIST_DIR}/concurrencppTargets.cmake") 5 | -------------------------------------------------------------------------------- /cmake/concurrencppInjectTSAN.cmake: -------------------------------------------------------------------------------- 1 | # Inject sanitizer flag as a build requirement 2 | # 3 | macro(add_library TARGET) 4 | _add_library(${ARGV}) 5 | 6 | if("${TARGET}" STREQUAL "concurrencpp") 7 | target_compile_options(concurrencpp PUBLIC -fsanitize=thread) 8 | target_link_options(concurrencpp PUBLIC -fsanitize=thread) 9 | endif() 10 | endmacro() 11 | -------------------------------------------------------------------------------- /cmake/coroutineOptions.cmake: -------------------------------------------------------------------------------- 1 | # Sets the required compiler options for the given target, or stops CMake if the 2 | # current compiler doesn't support coroutines. 3 | # 4 | function(target_coroutine_options TARGET) 5 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 6 | target_compile_options(${TARGET} PUBLIC /permissive-) 7 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 8 | set_target_properties(${TARGET} PROPERTIES CXX_EXTENSIONS NO) 9 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 10 | else() 11 | message(FATAL_ERROR "Compiler not supported: ${CMAKE_CXX_COMPILER_ID}") 12 | endif() 13 | endfunction() 14 | -------------------------------------------------------------------------------- /cmake/libc++.cmake: -------------------------------------------------------------------------------- 1 | # Specify this file as CMAKE_TOOLCHAIN_FILE when invoking CMake with Clang 2 | # to link to libc++ instead of libstdc++ 3 | 4 | string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++") 5 | string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++ -lc++abi") 6 | -------------------------------------------------------------------------------- /example/10_regular_timer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(10_regular_timer LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(10_regular_timer source/main.cpp) 12 | 13 | target_compile_features(10_regular_timer PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(10_regular_timer PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(10_regular_timer) 18 | -------------------------------------------------------------------------------- /example/10_regular_timer/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | int main() { 8 | concurrencpp::runtime runtime; 9 | std::atomic_size_t counter = 1; 10 | concurrencpp::timer timer = runtime.timer_queue()->make_timer(1500ms, 2000ms, runtime.thread_pool_executor(), [&] { 11 | const auto c = counter.fetch_add(1); 12 | std::cout << "timer was invoked for the " << c << "th time" << std::endl; 13 | }); 14 | 15 | std::cout << "timer due time (ms): " << timer.get_due_time().count() << std::endl; 16 | std::cout << "timer frequency (ms): " << timer.get_frequency().count() << std::endl; 17 | std::cout << "timer-associated executor : " << timer.get_executor()->name << std::endl; 18 | 19 | std::this_thread::sleep_for(20s); 20 | 21 | std::cout << "main thread cancelling timer" << std::endl; 22 | timer.cancel(); 23 | 24 | std::this_thread::sleep_for(10s); 25 | 26 | return 0; 27 | } -------------------------------------------------------------------------------- /example/11_oneshot_timer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(11_oneshot_time LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(11_oneshot_time source/main.cpp) 12 | 13 | target_compile_features(11_oneshot_time PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(11_oneshot_time PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(11_oneshot_time) 18 | -------------------------------------------------------------------------------- /example/11_oneshot_timer/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | using namespace std::chrono_literals; 6 | 7 | int main() { 8 | concurrencpp::runtime runtime; 9 | concurrencpp::timer timer = runtime.timer_queue()->make_one_shot_timer(3s, runtime.thread_executor(), [] { 10 | std::cout << "hello and goodbye" << std::endl; 11 | }); 12 | 13 | std::cout << "timer due time (ms): " << timer.get_due_time().count() << std::endl; 14 | std::cout << "timer frequency (ms): " << timer.get_frequency().count() << std::endl; 15 | std::cout << "timer-associated executor : " << timer.get_executor()->name << std::endl; 16 | 17 | std::this_thread::sleep_for(4s); 18 | return 0; 19 | } -------------------------------------------------------------------------------- /example/12_delay_object/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(12_delay_object LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(12_delay_object source/main.cpp) 12 | 13 | target_compile_features(12_delay_object PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(12_delay_object PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(12_delay_object) 18 | -------------------------------------------------------------------------------- /example/12_delay_object/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "concurrencpp/concurrencpp.h" 4 | 5 | using namespace std::chrono_literals; 6 | 7 | concurrencpp::null_result delayed_task(std::shared_ptr tq, 8 | std::shared_ptr ex) { 9 | size_t counter = 1; 10 | 11 | while (true) { 12 | std::cout << "task was invoked " << counter << " times." << std::endl; 13 | counter++; 14 | 15 | co_await tq->make_delay_object(1500ms, ex); 16 | } 17 | } 18 | 19 | int main() { 20 | concurrencpp::runtime runtime; 21 | delayed_task(runtime.timer_queue(), runtime.thread_pool_executor()); 22 | 23 | std::this_thread::sleep_for(10s); 24 | return 0; 25 | } -------------------------------------------------------------------------------- /example/13_generator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(13_generator LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(13_generator source/main.cpp) 12 | 13 | target_compile_features(13_generator PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(13_generator PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(13_generator) 18 | -------------------------------------------------------------------------------- /example/13_generator/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "concurrencpp/concurrencpp.h" 6 | 7 | concurrencpp::generator read_lines(std::string_view text) { 8 | std::string_view::size_type pos = 0; 9 | std::string_view::size_type prev = 0; 10 | 11 | while ((pos = text.find('\n', prev)) != std::string::npos) { 12 | co_yield text.substr(prev, pos - prev); 13 | prev = pos + 1; 14 | } 15 | 16 | co_yield text.substr(prev); 17 | } 18 | 19 | concurrencpp::result read_file_lines(const std::filesystem::path& path, 20 | std::shared_ptr background_executor, 21 | std::shared_ptr thread_pool_executor) { 22 | // make sure we don't block in a thread that is used for cpu-processing 23 | co_await concurrencpp::resume_on(background_executor); 24 | 25 | std::ifstream stream(path.c_str(), std::ios::binary | std::ios::in); 26 | std::string file_content(std::istreambuf_iterator(stream), {}); 27 | 28 | // make sure we don't process cpu-bound tasks on the background executor 29 | co_await concurrencpp::resume_on(thread_pool_executor); 30 | 31 | for (const auto& line : read_lines(file_content)) { 32 | std::cout << "read a new line. size: " << line.size() << std::endl; 33 | std::cout << "line: " << std::endl; 34 | std::cout << line; 35 | std::cout << "\n==============" << std::endl; 36 | } 37 | } 38 | 39 | int main(const int argc, const char* argv[]) { 40 | if (argc < 2) { 41 | const auto help_msg = "please pass all necessary arguments\n argv[1] - the file to be read\n"; 42 | std::cerr << help_msg << std::endl; 43 | return -1; 44 | } 45 | 46 | const auto file_path = std::string(argv[1]); 47 | 48 | concurrencpp::runtime runtime; 49 | const auto thread_pool_executor = runtime.thread_pool_executor(); 50 | const auto background_executor = runtime.background_executor(); 51 | 52 | read_file_lines(file_path, thread_pool_executor, background_executor).get(); 53 | return 0; 54 | } -------------------------------------------------------------------------------- /example/1_hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(1_hello_world LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(1_hello_world source/main.cpp) 12 | 13 | target_compile_features(1_hello_world PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(1_hello_world PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(1_hello_world) 18 | -------------------------------------------------------------------------------- /example/1_hello_world/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | #include 3 | 4 | int main() { 5 | concurrencpp::runtime runtime; 6 | auto result = runtime.thread_executor()->submit([] { 7 | std::cout << "hello world" << std::endl; 8 | }); 9 | 10 | result.get(); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /example/2_concurrent_even_number_counting/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(2_concurrent_even_number_counting LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(2_concurrent_even_number_counting source/main.cpp) 12 | 13 | target_compile_features(2_concurrent_even_number_counting PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(2_concurrent_even_number_counting PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(2_concurrent_even_number_counting) 18 | -------------------------------------------------------------------------------- /example/2_concurrent_even_number_counting/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | using namespace concurrencpp; 10 | 11 | std::vector make_random_vector() { 12 | std::vector vec(64 * 1'024); 13 | 14 | std::srand(std::time(nullptr)); 15 | for (auto& i : vec) { 16 | i = ::rand(); 17 | } 18 | 19 | return vec; 20 | } 21 | 22 | result count_even(std::shared_ptr tpe, const std::vector& vector) { 23 | const auto vecor_size = vector.size(); 24 | const auto concurrency_level = tpe->max_concurrency_level(); 25 | const auto chunk_size = vecor_size / concurrency_level; 26 | 27 | std::vector> chunk_count; 28 | 29 | for (auto i = 0; i < concurrency_level; i++) { 30 | const auto chunk_begin = i * chunk_size; 31 | const auto chunk_end = chunk_begin + chunk_size; 32 | auto result = tpe->submit([&vector, chunk_begin, chunk_end]() -> size_t { 33 | return std::count_if(vector.begin() + chunk_begin, vector.begin() + chunk_end, [](auto i) { 34 | return i % 2 == 0; 35 | }); 36 | }); 37 | 38 | chunk_count.emplace_back(std::move(result)); 39 | } 40 | 41 | size_t total_count = 0; 42 | 43 | for (auto& result : chunk_count) { 44 | total_count += co_await result; 45 | } 46 | 47 | co_return total_count; 48 | } 49 | 50 | int main() { 51 | concurrencpp::runtime runtime; 52 | const auto vector = make_random_vector(); 53 | auto result = count_even(runtime.thread_pool_executor(), vector); 54 | const auto total_count = result.get(); 55 | std::cout << "there are " << total_count << " even numbers in the vector" << std::endl; 56 | return 0; 57 | } -------------------------------------------------------------------------------- /example/3_async_file_processing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(3_async_file_processing LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(3_async_file_processing source/main.cpp) 12 | 13 | target_compile_features(3_async_file_processing PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(3_async_file_processing PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(3_async_file_processing) 18 | -------------------------------------------------------------------------------- /example/3_async_file_processing/source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | In this example, we will use concurrencpp executors to process a file asynchronously. 3 | This application will iterate the file characters (we assume 4 | ASCII) and replace a character with another. The application gets three 5 | parameters through the command line arguments: 6 | argv[0] - the path to the binary that created this process (not used in this example) 7 | argv[1] - a file path 8 | argv[2] - the character to replace 9 | argv[3] - the character to replace with. 10 | 11 | Since standard file streams are blocking, we would like to execute 12 | file-io operations using the background_executor, which its job is to execute 13 | relatively short-blocking tasks (like file-io). 14 | 15 | Processing the file content is a cpu-bound task (iterating over a binary 16 | buffer and potentially changing characters), so after reading the file we 17 | will resume execution in the thread_pool_executor, 18 | 19 | After the content has been modified, it is ready to be re-written back to 20 | the file. we will again schedule a blocking write operation to the 21 | background_executor. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "concurrencpp/concurrencpp.h" 30 | 31 | concurrencpp::result replace_chars_in_file(std::shared_ptr background_executor, 32 | std::shared_ptr threadpool_executor, 33 | const std::string file_path, 34 | char from, 35 | char to) { 36 | 37 | // tell the background executor to read a whole file to a buffer and return it 38 | auto file_content = co_await background_executor->submit([file_path] { 39 | std::ifstream input; 40 | input.exceptions(std::ifstream::badbit); 41 | input.open(file_path, std::ios::binary); 42 | std::vector buffer(std::istreambuf_iterator(input), {}); 43 | return buffer; 44 | }); 45 | 46 | // tell the threadpool executor to process the file 47 | auto processed_file_content = co_await threadpool_executor->submit([file_content = std::move(file_content), from, to]() mutable { 48 | for (auto& c : file_content) { 49 | if (c == from) { 50 | c = to; 51 | } 52 | } 53 | 54 | return std::move(file_content); 55 | }); 56 | 57 | // schedule the write operation on the background executor and await it to finish. 58 | co_await background_executor->submit([file_path, file_content = std::move(processed_file_content)] { 59 | std::ofstream output; 60 | output.exceptions(std::ofstream::badbit); 61 | output.open(file_path, std::ios::binary); 62 | output.write(file_content.data(), file_content.size()); 63 | }); 64 | 65 | std::cout << "file has been modified successfully" << std::endl; 66 | } 67 | 68 | int main(int argc, const char* argv[]) { 69 | if (argc < 4) { 70 | const auto help_msg = "please pass all necessary arguments\n\ 71 | argv[1] - the file to process\n\ 72 | argv[2] - the character to replace\n\ 73 | argv[3] - the character to replace with"; 74 | 75 | std::cerr << help_msg << std::endl; 76 | return -1; 77 | } 78 | 79 | if (std::strlen(argv[2]) != 1 || std::strlen(argv[3]) != 1) { 80 | std::cerr << "argv[2] and argv[3] must be one character only" << std::endl; 81 | return -1; 82 | } 83 | 84 | const auto file_path = std::string(argv[1]); 85 | const auto from_char = argv[2][0]; 86 | const auto to_char = argv[3][0]; 87 | 88 | concurrencpp::runtime runtime; 89 | 90 | try { 91 | replace_chars_in_file(runtime.background_executor(), runtime.thread_pool_executor(), file_path, from_char, to_char).get(); 92 | } catch (const std::exception& e) { 93 | std::cerr << e.what() << std::endl; 94 | } 95 | 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /example/4_async_file_processing_version_2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(4_async_file_processing_version_2 LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(4_async_file_processing_version_2 source/main.cpp) 12 | 13 | target_compile_features(4_async_file_processing_version_2 PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(4_async_file_processing_version_2 PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(4_async_file_processing_version_2) 18 | -------------------------------------------------------------------------------- /example/4_async_file_processing_version_2/source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This example shows another version of processing a file asynchronously but this time 3 | we will use concurrencpp::resume_on to explicitly switch execution between executors. 4 | 5 | First we will reschedule the replace_chars_in_file task to run on the background executor in order to read 6 | the file. Then we will reschedule the task to run on the threadpool executor and replace the unwanted character. 7 | lastly we will reschedule the task to run again on the background executor in order to write the processed 8 | content back ot the file. 9 | 10 | The original version of this application does this rescheduling implicitly, by awaiting results that executor::submit 11 | produces. this version explicitly switches execution between executors and does not create sub-tasks from lambdas. 12 | Both versions are identical in terms of functionality and the final outcome. 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "concurrencpp/concurrencpp.h" 21 | 22 | concurrencpp::result replace_chars_in_file(std::shared_ptr background_executor, 23 | std::shared_ptr threadpool_executor, 24 | const std::string file_path, 25 | char from, 26 | char to) { 27 | // switch control from whatever executor/thread this coroutine was called from to the background_executor. 28 | co_await concurrencpp::resume_on(background_executor); 29 | 30 | // we're running on the background executor now. we can safely block. 31 | std::vector file_content; 32 | 33 | { 34 | std::ifstream input; 35 | input.exceptions(std::ifstream::badbit); 36 | input.open(file_path, std::ios::binary); 37 | file_content.assign(std::istreambuf_iterator(input), {}); 38 | } 39 | 40 | // switch execution to the threadpool-executor 41 | co_await concurrencpp::resume_on(threadpool_executor); 42 | 43 | // we're running on the threadpool executor now. we can process CPU-bound tasks now. 44 | for (auto& c : file_content) { 45 | if (c == from) { 46 | c = to; 47 | } 48 | } 49 | 50 | // switch execution back to the background-executor 51 | concurrencpp::resume_on(background_executor); 52 | 53 | // write the processed file content. since we're running on the background executor, it's safe to block. 54 | std::ofstream output; 55 | output.exceptions(std::ofstream::badbit); 56 | output.open(file_path, std::ios::binary); 57 | output.write(file_content.data(), file_content.size()); 58 | 59 | std::cout << "file has been modified successfully" << std::endl; 60 | } 61 | 62 | int main(int argc, const char* argv[]) { 63 | if (argc < 4) { 64 | const auto help_msg = "please pass all necessary arguments\n\ 65 | argv[1] - the file to process\n\ 66 | argv[2] - the character to replace\n\ 67 | argv[3] - the character to replace with"; 68 | 69 | std::cerr << help_msg << std::endl; 70 | return -1; 71 | } 72 | 73 | if (std::strlen(argv[2]) != 1 || std::strlen(argv[3]) != 1) { 74 | std::cerr << "argv[2] and argv[3] must be one character only" << std::endl; 75 | return -1; 76 | } 77 | 78 | const auto file_path = std::string(argv[1]); 79 | const auto from_char = argv[2][0]; 80 | const auto to_char = argv[3][0]; 81 | 82 | concurrencpp::runtime runtime; 83 | 84 | try { 85 | replace_chars_in_file(runtime.background_executor(), runtime.thread_pool_executor(), file_path, from_char, to_char).get(); 86 | } catch (const std::exception& e) { 87 | std::cerr << e.what() << std::endl; 88 | } 89 | 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /example/5_prime_number_finder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(5_prime_number_finder LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(5_prime_number_finder source/main.cpp) 12 | 13 | target_compile_features(5_prime_number_finder PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(5_prime_number_finder PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(5_prime_number_finder) 18 | -------------------------------------------------------------------------------- /example/5_prime_number_finder/source/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | In this example we will collect all prime numbers from 0 to 1,000,000 in a parallel manner, using parallel coroutines. 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "concurrencpp/concurrencpp.h" 9 | 10 | bool is_prime(int num) { 11 | if (num <= 1) { 12 | return false; 13 | } 14 | 15 | if (num <= 3) { 16 | return true; 17 | } 18 | 19 | const auto range = static_cast(std::sqrt(num)); 20 | if (num % 2 == 0 || num % 3 == 0) { 21 | return false; 22 | } 23 | 24 | for (int i = 5; i <= range; i += 6) { 25 | if (num % i == 0 || num % (i + 2) == 0) { 26 | return false; 27 | } 28 | } 29 | 30 | return true; 31 | } 32 | 33 | // runs in parallel 34 | concurrencpp::result> find_primes(concurrencpp::executor_tag, 35 | std::shared_ptr executor, 36 | int begin, 37 | int end) { 38 | assert(begin <= end); 39 | 40 | std::vector prime_numbers; 41 | for (int i = begin; i < end; i++) { 42 | if (is_prime(i)) { 43 | prime_numbers.emplace_back(i); 44 | } 45 | } 46 | 47 | co_return prime_numbers; 48 | } 49 | 50 | concurrencpp::result> find_prime_numbers(std::shared_ptr executor) { 51 | const auto concurrency_level = executor->max_concurrency_level(); 52 | const auto range_length = 1'000'000 / concurrency_level; 53 | 54 | std::vector>> found_primes_in_range; 55 | int range_begin = 0; 56 | 57 | for (int i = 0; i < concurrency_level; i++) { 58 | auto result = find_primes({}, executor, range_begin, range_begin + range_length); 59 | range_begin += range_length; 60 | 61 | found_primes_in_range.emplace_back(std::move(result)); 62 | } 63 | 64 | auto all_done = co_await concurrencpp::when_all(executor, found_primes_in_range.begin(), found_primes_in_range.end()); 65 | 66 | std::vector found_primes; 67 | for (auto& done_result : all_done) { 68 | auto prime_numbers = co_await done_result; 69 | found_primes.insert(found_primes.end(), prime_numbers.begin(), prime_numbers.end()); 70 | } 71 | 72 | co_return found_primes; 73 | } 74 | 75 | int main(int argc, const char* argv[]) { 76 | concurrencpp::runtime runtime; 77 | auto all_prime_numbers = find_prime_numbers(runtime.thread_pool_executor()).get(); 78 | 79 | for (auto i : all_prime_numbers) { 80 | std::cout << i << std::endl; 81 | } 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /example/6_manual_executor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(6_manual_executor LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(6_manual_executor source/main.cpp) 12 | 13 | target_compile_features(6_manual_executor PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(6_manual_executor PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(6_manual_executor) 18 | -------------------------------------------------------------------------------- /example/6_manual_executor/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "concurrencpp/concurrencpp.h" 5 | 6 | int main(int argc, const char* argv[]) { 7 | concurrencpp::runtime runtime; 8 | auto manual_executor = runtime.make_manual_executor(); 9 | 10 | // This represents asynchronous, non-concurrencpp workers and agents that an application may use. 11 | // It is recommended to simply use other concurrencpp executors instead of creating raw threads and 12 | // user defined executors. 13 | std::vector threads; 14 | 15 | threads.emplace_back([manual_executor] { 16 | const auto tasks_executed = manual_executor->loop_for(10, std::chrono::seconds(30)); 17 | std::cout << tasks_executed << " tasks were executed on thread " << std::this_thread::get_id() << std::endl; 18 | }); 19 | 20 | threads.emplace_back([manual_executor] { 21 | const auto task_executed = manual_executor->loop_once_for(std::chrono::seconds(30)); 22 | std::cout << (task_executed ? "one " : "no ") << "task was executed on thread " << std::this_thread::get_id() << std::endl; 23 | }); 24 | 25 | threads.emplace_back([manual_executor] { 26 | std::cout << "thread id " << std::this_thread::get_id() << " is waiting for tasks to bo enqueued" << std::endl; 27 | auto num_of_tasks = manual_executor->wait_for_tasks_for(50, std::chrono::seconds(30)); 28 | std::cout << "thread id " << std::this_thread::get_id() << ", there are " << num_of_tasks 29 | << " enqueued tasks when returning from manual_executor::wait_for_tasks_for" << std::endl; 30 | 31 | num_of_tasks = manual_executor->loop(50); 32 | std::cout << "thread id " << std::this_thread::get_id() << ", executed " << num_of_tasks << " tasks " << std::endl; 33 | }); 34 | 35 | struct dummy_task { 36 | void operator()() const noexcept { 37 | std::cout << "A dummy task is being executed on thread_id " << std::this_thread::get_id() << std::endl; 38 | } 39 | }; 40 | 41 | threads.emplace_back([manual_executor] { 42 | std::vector tasks(20); 43 | 44 | std::this_thread::sleep_for(std::chrono::seconds(5)); 45 | 46 | std::cout << "thread_id " << std::this_thread::get_id() << " is posting " << tasks.size() << " tasks." << std::endl; 47 | manual_executor->bulk_post(tasks); 48 | }); 49 | 50 | threads.emplace_back([manual_executor] { 51 | std::vector tasks(30); 52 | 53 | std::this_thread::sleep_for(std::chrono::seconds(12)); 54 | 55 | std::cout << "thread_id " << std::this_thread::get_id() << " is posting " << tasks.size() << " tasks." << std::endl; 56 | manual_executor->bulk_post(tasks); 57 | }); 58 | 59 | threads.emplace_back([manual_executor] { 60 | std::vector tasks(10); 61 | 62 | std::this_thread::sleep_for(std::chrono::seconds(18)); 63 | 64 | std::cout << "thread_id " << std::this_thread::get_id() << " is posting " << tasks.size() << " tasks." << std::endl; 65 | manual_executor->bulk_post(tasks); 66 | }); 67 | 68 | std::this_thread::sleep_for(std::chrono::seconds(35)); 69 | 70 | for (auto& thread : threads) { 71 | thread.join(); 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /example/7_when_all/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(7_when_all LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(7_when_all source/main.cpp) 12 | 13 | target_compile_features(7_when_all PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(7_when_all PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(7_when_all) 18 | -------------------------------------------------------------------------------- /example/7_when_all/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "concurrencpp/concurrencpp.h" 8 | 9 | int example_job(int task_num, int dummy_value, int sleeping_time_ms) { 10 | const auto msg0 = 11 | std::string("task num: ") + std::to_string(task_num) + " is going to sleep for " + std::to_string(sleeping_time_ms) + " ms"; 12 | std::cout << msg0 << std::endl; 13 | 14 | std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time_ms)); 15 | 16 | const auto msg1 = std::string("task num: ") + std::to_string(task_num) + " woke up."; 17 | std::cout << msg1 << std::endl; 18 | 19 | return dummy_value; 20 | } 21 | 22 | concurrencpp::result consume_all_tasks(std::shared_ptr resume_executor, 23 | std::vector> results) { 24 | auto all_done = co_await concurrencpp::when_all(resume_executor, results.begin(), results.end()); 25 | 26 | for (auto& done_result : all_done) { 27 | std::cout << co_await done_result << std::endl; 28 | } 29 | 30 | std::cout << "all tasks were consumed" << std::endl; 31 | } 32 | 33 | int main(int argc, const char* argv[]) { 34 | concurrencpp::runtime runtime; 35 | auto background_executor = runtime.background_executor(); 36 | auto thread_pool_executor = runtime.thread_pool_executor(); 37 | std::vector> results; 38 | 39 | std::srand(static_cast(std::time(nullptr))); 40 | 41 | for (int i = 0; i < 10; i++) { 42 | const auto sleeping_time_ms = std::rand() % 1000; 43 | results.emplace_back(background_executor->submit(example_job, i, i * 15, sleeping_time_ms)); 44 | } 45 | 46 | consume_all_tasks(thread_pool_executor, std::move(results)).get(); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /example/8_when_any/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(8_when_any LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(8_when_any source/main.cpp) 12 | 13 | target_compile_features(8_when_any PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(8_when_any PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(8_when_any) 18 | -------------------------------------------------------------------------------- /example/8_when_any/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "concurrencpp/concurrencpp.h" 8 | 9 | int example_job(int task_num, int dummy_value, int sleeping_time_ms) { 10 | const auto msg0 = 11 | std::string("task num: ") + std::to_string(task_num) + " is going to sleep for " + std::to_string(sleeping_time_ms) + " ms"; 12 | std::cout << msg0 << std::endl; 13 | 14 | std::this_thread::sleep_for(std::chrono::milliseconds(sleeping_time_ms)); 15 | 16 | const auto msg1 = std::string("task num: ") + std::to_string(task_num) + " woke up."; 17 | std::cout << msg1 << std::endl; 18 | 19 | return dummy_value; 20 | } 21 | 22 | concurrencpp::result consume_tasks_as_they_finish(std::shared_ptr resume_executor, 23 | std::vector> results) { 24 | while (!results.empty()) { 25 | auto when_any = co_await concurrencpp::when_any(resume_executor, results.begin(), results.end()); 26 | auto finished_task = std::move(when_any.results[when_any.index]); 27 | 28 | const auto done_value = co_await finished_task; 29 | 30 | auto msg = 31 | std::string("task num: ") + std::to_string(when_any.index) + " is finished with value of: " + std::to_string(done_value); 32 | 33 | std::cout << msg << std::endl; 34 | 35 | results = std::move(when_any.results); 36 | results.erase(results.begin() + when_any.index); 37 | } 38 | } 39 | 40 | int main(int argc, const char* argv[]) { 41 | concurrencpp::runtime runtime; 42 | auto background_executor = runtime.background_executor(); 43 | auto thread_pool_executor = runtime.thread_pool_executor(); 44 | std::vector> results; 45 | 46 | std::srand(static_cast(std::time(nullptr))); 47 | 48 | for (int i = 0; i < 10; i++) { 49 | const auto sleeping_time_ms = std::rand() % 1000; 50 | results.emplace_back(background_executor->submit(example_job, i, i * 15, sleeping_time_ms)); 51 | } 52 | 53 | consume_tasks_as_they_finish(thread_pool_executor, std::move(results)).get(); 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /example/9_result_promise/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(9_result_promise LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(9_result_promise source/main.cpp) 12 | 13 | target_compile_features(9_result_promise PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(9_result_promise PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(9_result_promise) 18 | -------------------------------------------------------------------------------- /example/9_result_promise/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | int main() { 9 | concurrencpp::result_promise promise; 10 | auto result = promise.get_result(); 11 | 12 | std::thread my_3_party_executor([promise = std::move(promise)]() mutable { 13 | std::this_thread::sleep_for(std::chrono::seconds(1)); // imitate real work 14 | 15 | // imitate a random failure 16 | std::srand(static_cast(::time(nullptr))); 17 | if (std::rand() % 100 < 90) { 18 | promise.set_result("hello world"); 19 | } else { 20 | promise.set_exception(std::make_exception_ptr(std::runtime_error("failure"))); 21 | } 22 | }); 23 | 24 | try { 25 | auto asynchronous_string = result.get(); 26 | std::cout << "result promise returned string: " << asynchronous_string << std::endl; 27 | 28 | } catch (const std::exception& e) { 29 | std::cerr << "An exception was thrown while executing asynchronous function: " << e.what() << std::endl; 30 | } 31 | 32 | my_3_party_executor.join(); 33 | } -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(concurrencppExamples LANGUAGES CXX) 4 | 5 | foreach(example IN ITEMS 6 | 1_hello_world 7 | 2_concurrent_even_number_counting 8 | 3_async_file_processing 9 | 4_async_file_processing_version_2 10 | 5_prime_number_finder 11 | 6_manual_executor 12 | 7_when_all 13 | 8_when_any 14 | 9_result_promise 15 | 10_regular_timer 16 | 11_oneshot_timer 17 | 12_delay_object 18 | 13_generator 19 | ) 20 | add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/${example}" 21 | "${CMAKE_CURRENT_BINARY_DIR}/${example}") 22 | endforeach() 23 | -------------------------------------------------------------------------------- /include/concurrencpp/concurrencpp.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_H 2 | #define CONCURRENCPP_H 3 | 4 | #include "concurrencpp/forward_declarations.h" 5 | #include "concurrencpp/platform_defs.h" 6 | 7 | #include "concurrencpp/timers/timer.h" 8 | #include "concurrencpp/timers/timer_queue.h" 9 | #include "concurrencpp/runtime/runtime.h" 10 | #include "concurrencpp/results/result.h" 11 | #include "concurrencpp/results/lazy_result.h" 12 | #include "concurrencpp/results/make_result.h" 13 | #include "concurrencpp/results/when_result.h" 14 | #include "concurrencpp/results/shared_result.h" 15 | #include "concurrencpp/results/shared_result_awaitable.h" 16 | #include "concurrencpp/results/promises.h" 17 | #include "concurrencpp/results/resume_on.h" 18 | #include "concurrencpp/results/generator.h" 19 | #include "concurrencpp/executors/executor_all.h" 20 | #include "concurrencpp/threads/async_lock.h" 21 | #include "concurrencpp/threads/async_condition_variable.h" 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/concurrencpp/coroutines/coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_COROUTINE_H 2 | #define CONCURRENCPP_COROUTINE_H 3 | 4 | #include "../platform_defs.h" 5 | 6 | #if !__has_include() && __has_include() 7 | 8 | # include 9 | # define CRCPP_COROUTINE_NAMESPACE std::experimental 10 | 11 | #else 12 | 13 | # include 14 | # define CRCPP_COROUTINE_NAMESPACE std 15 | 16 | #endif 17 | 18 | namespace concurrencpp::details { 19 | template 20 | using coroutine_handle = CRCPP_COROUTINE_NAMESPACE::coroutine_handle; 21 | using suspend_never = CRCPP_COROUTINE_NAMESPACE::suspend_never; 22 | using suspend_always = CRCPP_COROUTINE_NAMESPACE::suspend_always; 23 | } // namespace concurrencpp::details 24 | 25 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_ERRORS_H 2 | #define CONCURRENCPP_ERRORS_H 3 | 4 | #include 5 | 6 | namespace concurrencpp::errors { 7 | struct CRCPP_API empty_object : public std::runtime_error { 8 | using runtime_error::runtime_error; 9 | }; 10 | 11 | struct CRCPP_API empty_result : public empty_object { 12 | using empty_object::empty_object; 13 | }; 14 | 15 | struct CRCPP_API empty_result_promise : public empty_object { 16 | using empty_object::empty_object; 17 | }; 18 | 19 | struct CRCPP_API empty_awaitable : public empty_object { 20 | using empty_object::empty_object; 21 | }; 22 | 23 | struct CRCPP_API empty_timer : public empty_object { 24 | using empty_object::empty_object; 25 | }; 26 | 27 | struct CRCPP_API empty_generator : public empty_object { 28 | using empty_object::empty_object; 29 | }; 30 | 31 | struct CRCPP_API interrupted_task : public std::runtime_error { 32 | using runtime_error::runtime_error; 33 | }; 34 | 35 | struct CRCPP_API broken_task : public interrupted_task { 36 | using interrupted_task::interrupted_task; 37 | }; 38 | 39 | struct CRCPP_API runtime_shutdown : public interrupted_task { 40 | using interrupted_task::interrupted_task; 41 | }; 42 | 43 | struct CRCPP_API result_already_retrieved : public std::runtime_error { 44 | using runtime_error::runtime_error; 45 | }; 46 | } // namespace concurrencpp::errors 47 | 48 | #endif // ERRORS_H 49 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_EXECUTORS_CONSTS_H 2 | #define CONCURRENCPP_EXECUTORS_CONSTS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::details::consts { 8 | inline const char* k_inline_executor_name = "concurrencpp::inline_executor"; 9 | constexpr int k_inline_executor_max_concurrency_level = 0; 10 | 11 | inline const char* k_thread_executor_name = "concurrencpp::thread_executor"; 12 | constexpr int k_thread_executor_max_concurrency_level = std::numeric_limits::max(); 13 | 14 | inline const char* k_thread_pool_executor_name = "concurrencpp::thread_pool_executor"; 15 | inline const char* k_background_executor_name = "concurrencpp::background_executor"; 16 | 17 | constexpr int k_worker_thread_max_concurrency_level = 1; 18 | inline const char* k_worker_thread_executor_name = "concurrencpp::worker_thread_executor"; 19 | 20 | inline const char* k_manual_executor_name = "concurrencpp::manual_executor"; 21 | constexpr int k_manual_executor_max_concurrency_level = std::numeric_limits::max(); 22 | 23 | inline const char* k_timer_queue_name = "concurrencpp::timer_queue"; 24 | 25 | inline const char* k_executor_shutdown_err_msg = " - shutdown has been called on this executor."; 26 | } // namespace concurrencpp::details::consts 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/derivable_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_DERIVABLE_EXECUTOR_H 2 | #define CONCURRENCPP_DERIVABLE_EXECUTOR_H 3 | 4 | #include "concurrencpp/utils/bind.h" 5 | #include "concurrencpp/executors/executor.h" 6 | 7 | namespace concurrencpp { 8 | template 9 | struct CRCPP_API derivable_executor : public executor { 10 | 11 | derivable_executor(std::string_view name) : executor(name) {} 12 | 13 | template 14 | void post(callable_type&& callable, argument_types&&... arguments) { 15 | return do_post(std::forward(callable), std::forward(arguments)...); 16 | } 17 | 18 | template 19 | auto submit(callable_type&& callable, argument_types&&... arguments) { 20 | return do_submit(std::forward(callable), 21 | std::forward(arguments)...); 22 | } 23 | 24 | template 25 | void bulk_post(std::span callable_list) { 26 | return do_bulk_post(callable_list); 27 | } 28 | 29 | template> 30 | std::vector> bulk_submit(std::span callable_list) { 31 | return do_bulk_submit(callable_list); 32 | } 33 | }; 34 | } // namespace concurrencpp 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/executor_all.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_EXECUTORS_ALL_H 2 | #define CONCURRENCPP_EXECUTORS_ALL_H 3 | 4 | #include "concurrencpp/executors/derivable_executor.h" 5 | #include "concurrencpp/executors/inline_executor.h" 6 | #include "concurrencpp/executors/thread_pool_executor.h" 7 | #include "concurrencpp/executors/thread_executor.h" 8 | #include "concurrencpp/executors/worker_thread_executor.h" 9 | #include "concurrencpp/executors/manual_executor.h" 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/inline_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_INLINE_EXECUTOR_H 2 | #define CONCURRENCPP_INLINE_EXECUTOR_H 3 | 4 | #include "concurrencpp/executors/executor.h" 5 | #include "concurrencpp/executors/constants.h" 6 | 7 | namespace concurrencpp { 8 | class CRCPP_API inline_executor final : public executor { 9 | 10 | private: 11 | std::atomic_bool m_abort; 12 | 13 | void throw_if_aborted() const { 14 | if (m_abort.load(std::memory_order_relaxed)) { 15 | details::throw_runtime_shutdown_exception(name); 16 | } 17 | } 18 | 19 | public: 20 | inline_executor() noexcept : executor(details::consts::k_inline_executor_name), m_abort(false) {} 21 | 22 | void enqueue(concurrencpp::task task) override { 23 | throw_if_aborted(); 24 | task(); 25 | } 26 | 27 | void enqueue(std::span tasks) override { 28 | throw_if_aborted(); 29 | for (auto& task : tasks) { 30 | task(); 31 | } 32 | } 33 | 34 | int max_concurrency_level() const noexcept override { 35 | return details::consts::k_inline_executor_max_concurrency_level; 36 | } 37 | 38 | void shutdown() override { 39 | m_abort.store(true, std::memory_order_relaxed); 40 | } 41 | 42 | bool shutdown_requested() const override { 43 | return m_abort.load(std::memory_order_relaxed); 44 | } 45 | }; 46 | } // namespace concurrencpp 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/manual_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_MANUAL_EXECUTOR_H 2 | #define CONCURRENCPP_MANUAL_EXECUTOR_H 3 | 4 | #include "concurrencpp/threads/cache_line.h" 5 | #include "concurrencpp/executors/derivable_executor.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace concurrencpp { 13 | class CRCPP_API alignas(CRCPP_CACHE_LINE_ALIGNMENT) manual_executor final : public derivable_executor { 14 | 15 | private: 16 | mutable std::mutex m_lock; 17 | std::deque m_tasks; 18 | std::condition_variable m_condition; 19 | bool m_abort; 20 | std::atomic_bool m_atomic_abort; 21 | 22 | template 23 | static std::chrono::system_clock::time_point to_system_time_point( 24 | std::chrono::time_point time_point) noexcept(noexcept(clock_type::now())) { 25 | const auto src_now = clock_type::now(); 26 | const auto dst_now = std::chrono::system_clock::now(); 27 | return dst_now + std::chrono::duration_cast(time_point - src_now); 28 | } 29 | 30 | static std::chrono::system_clock::time_point time_point_from_now(std::chrono::milliseconds ms) noexcept { 31 | return std::chrono::system_clock::now() + ms; 32 | } 33 | 34 | size_t loop_impl(size_t max_count); 35 | size_t loop_until_impl(size_t max_count, std::chrono::time_point deadline); 36 | 37 | void wait_for_tasks_impl(size_t count); 38 | size_t wait_for_tasks_impl(size_t count, std::chrono::time_point deadline); 39 | 40 | public: 41 | manual_executor(); 42 | 43 | void enqueue(task task) override; 44 | void enqueue(std::span tasks) override; 45 | 46 | int max_concurrency_level() const noexcept override; 47 | 48 | void shutdown() override; 49 | bool shutdown_requested() const override; 50 | 51 | size_t size() const; 52 | bool empty() const; 53 | 54 | size_t clear(); 55 | 56 | bool loop_once(); 57 | bool loop_once_for(std::chrono::milliseconds max_waiting_time); 58 | 59 | template 60 | bool loop_once_until(std::chrono::time_point timeout_time) { 61 | return loop_until_impl(1, to_system_time_point(timeout_time)); 62 | } 63 | 64 | size_t loop(size_t max_count); 65 | size_t loop_for(size_t max_count, std::chrono::milliseconds max_waiting_time); 66 | 67 | template 68 | size_t loop_until(size_t max_count, std::chrono::time_point timeout_time) { 69 | return loop_until_impl(max_count, to_system_time_point(timeout_time)); 70 | } 71 | 72 | void wait_for_task(); 73 | bool wait_for_task_for(std::chrono::milliseconds max_waiting_time); 74 | 75 | template 76 | bool wait_for_task_until(std::chrono::time_point timeout_time) { 77 | return wait_for_tasks_impl(1, to_system_time_point(timeout_time)) == 1; 78 | } 79 | 80 | void wait_for_tasks(size_t count); 81 | size_t wait_for_tasks_for(size_t count, std::chrono::milliseconds max_waiting_time); 82 | 83 | template 84 | size_t wait_for_tasks_until(size_t count, std::chrono::time_point timeout_time) { 85 | return wait_for_tasks_impl(count, to_system_time_point(timeout_time)); 86 | } 87 | }; 88 | } // namespace concurrencpp 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/thread_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_THREAD_EXECUTOR_H 2 | #define CONCURRENCPP_THREAD_EXECUTOR_H 3 | 4 | #include "concurrencpp/threads/thread.h" 5 | #include "concurrencpp/threads/cache_line.h" 6 | #include "concurrencpp/executors/derivable_executor.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace concurrencpp { 14 | class CRCPP_API alignas(CRCPP_CACHE_LINE_ALIGNMENT) thread_executor final : public derivable_executor { 15 | 16 | private: 17 | std::mutex m_lock; 18 | std::list m_workers; 19 | std::condition_variable m_condition; 20 | std::list m_last_retired; 21 | bool m_abort; 22 | std::atomic_bool m_atomic_abort; 23 | const std::function m_thread_started_callback; 24 | const std::function m_thread_terminated_callback; 25 | 26 | void enqueue_impl(std::unique_lock& lock, task& task); 27 | void retire_worker(std::list::iterator it); 28 | 29 | public: 30 | thread_executor(const std::function& thread_started_callback = {}, 31 | const std::function& thread_terminated_callback = {}); 32 | ~thread_executor() noexcept; 33 | 34 | void enqueue(task task) override; 35 | void enqueue(std::span tasks) override; 36 | 37 | int max_concurrency_level() const noexcept override; 38 | 39 | bool shutdown_requested() const override; 40 | void shutdown() override; 41 | }; 42 | } // namespace concurrencpp 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/thread_pool_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_THREAD_POOL_EXECUTOR_H 2 | #define CONCURRENCPP_THREAD_POOL_EXECUTOR_H 3 | 4 | #include "concurrencpp/threads/thread.h" 5 | #include "concurrencpp/threads/cache_line.h" 6 | #include "concurrencpp/executors/derivable_executor.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace concurrencpp::details { 12 | class idle_worker_set { 13 | 14 | enum class status { active, idle }; 15 | 16 | struct alignas(CRCPP_CACHE_LINE_ALIGNMENT) padded_flag { 17 | std::atomic flag {status::active}; 18 | }; 19 | 20 | private: 21 | std::atomic_intptr_t m_approx_size; 22 | const std::unique_ptr m_idle_flags; 23 | const size_t m_size; 24 | 25 | bool try_acquire_flag(size_t index) noexcept; 26 | 27 | public: 28 | idle_worker_set(size_t size); 29 | 30 | void set_idle(size_t idle_thread) noexcept; 31 | void set_active(size_t idle_thread) noexcept; 32 | 33 | size_t find_idle_worker(size_t caller_index) noexcept; 34 | void find_idle_workers(size_t caller_index, std::vector& result_buffer, size_t max_count) noexcept; 35 | }; 36 | } // namespace concurrencpp::details 37 | 38 | namespace concurrencpp::details { 39 | class thread_pool_worker; 40 | } // namespace concurrencpp::details 41 | 42 | namespace concurrencpp { 43 | class CRCPP_API alignas(CRCPP_CACHE_LINE_ALIGNMENT) thread_pool_executor final : public derivable_executor { 44 | 45 | friend class details::thread_pool_worker; 46 | 47 | private: 48 | std::vector m_workers; 49 | alignas(CRCPP_CACHE_LINE_ALIGNMENT) std::atomic_size_t m_round_robin_cursor; 50 | alignas(CRCPP_CACHE_LINE_ALIGNMENT) details::idle_worker_set m_idle_workers; 51 | alignas(CRCPP_CACHE_LINE_ALIGNMENT) std::atomic_bool m_abort; 52 | 53 | void mark_worker_idle(size_t index) noexcept; 54 | void mark_worker_active(size_t index) noexcept; 55 | void find_idle_workers(size_t caller_index, std::vector& buffer, size_t max_count) noexcept; 56 | 57 | details::thread_pool_worker& worker_at(size_t index) noexcept; 58 | 59 | public: 60 | thread_pool_executor(std::string_view pool_name, 61 | size_t pool_size, 62 | std::chrono::milliseconds max_idle_time, 63 | const std::function& thread_started_callback = {}, 64 | const std::function& thread_terminated_callback = {}); 65 | 66 | ~thread_pool_executor() override; 67 | 68 | void enqueue(task task) override; 69 | void enqueue(std::span tasks) override; 70 | 71 | int max_concurrency_level() const noexcept override; 72 | 73 | bool shutdown_requested() const override; 74 | void shutdown() override; 75 | 76 | std::chrono::milliseconds max_worker_idle_time() const noexcept; 77 | }; 78 | } // namespace concurrencpp 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /include/concurrencpp/executors/worker_thread_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_WORKER_THREAD_EXECUTOR_H 2 | #define CONCURRENCPP_WORKER_THREAD_EXECUTOR_H 3 | 4 | #include "concurrencpp/threads/thread.h" 5 | #include "concurrencpp/threads/cache_line.h" 6 | #include "concurrencpp/executors/derivable_executor.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace concurrencpp { 13 | class CRCPP_API alignas(CRCPP_CACHE_LINE_ALIGNMENT) worker_thread_executor final : 14 | public derivable_executor { 15 | 16 | private: 17 | std::deque m_private_queue; 18 | std::atomic_bool m_private_atomic_abort; 19 | alignas(CRCPP_CACHE_LINE_ALIGNMENT) std::mutex m_lock; 20 | std::deque m_public_queue; 21 | std::binary_semaphore m_semaphore; 22 | details::thread m_thread; 23 | std::atomic_bool m_atomic_abort; 24 | bool m_abort; 25 | const std::function m_thread_started_callback; 26 | const std::function m_thread_terminated_callback; 27 | 28 | void make_os_worker_thread(); 29 | bool drain_queue_impl(); 30 | bool drain_queue(); 31 | void wait_for_task(std::unique_lock& lock); 32 | void work_loop(); 33 | 34 | void enqueue_local(concurrencpp::task& task); 35 | void enqueue_local(std::span task); 36 | 37 | void enqueue_foreign(concurrencpp::task& task); 38 | void enqueue_foreign(std::span task); 39 | 40 | public: 41 | worker_thread_executor(const std::function& thread_started_callback = {}, 42 | const std::function& thread_terminated_callback = {}); 43 | 44 | void enqueue(concurrencpp::task task) override; 45 | void enqueue(std::span tasks) override; 46 | 47 | int max_concurrency_level() const noexcept override; 48 | 49 | bool shutdown_requested() const override; 50 | void shutdown() override; 51 | }; 52 | } // namespace concurrencpp 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/concurrencpp/forward_declarations.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_FORWARD_DECLARATIONS_H 2 | #define CONCURRENCPP_FORWARD_DECLARATIONS_H 3 | 4 | namespace concurrencpp { 5 | struct null_result; 6 | 7 | template 8 | class result; 9 | 10 | template 11 | class lazy_result; 12 | 13 | template 14 | class shared_result; 15 | 16 | template 17 | class result_promise; 18 | 19 | class runtime; 20 | 21 | class timer_queue; 22 | class timer; 23 | 24 | class executor; 25 | class inline_executor; 26 | class thread_pool_executor; 27 | class thread_executor; 28 | class worker_thread_executor; 29 | class manual_executor; 30 | 31 | template 32 | class generator; 33 | 34 | class async_lock; 35 | class async_condition_variable; 36 | } // namespace concurrencpp 37 | 38 | #endif // FORWARD_DECLARATIONS_H 39 | -------------------------------------------------------------------------------- /include/concurrencpp/platform_defs.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_PLATFORM_DEFS_H 2 | #define CONCURRENCPP_PLATFORM_DEFS_H 3 | 4 | #if defined(__MINGW32__) 5 | # define CRCPP_MINGW_OS 6 | #elif defined(_WIN32) 7 | # define CRCPP_WIN_OS 8 | #elif defined(unix) || defined(__unix__) || defined(__unix) 9 | # define CRCPP_UNIX_OS 10 | #elif defined(__APPLE__) || defined(__MACH__) 11 | # define CRCPP_MAC_OS 12 | #elif defined(__FreeBSD__) 13 | # define CRCPP_FREE_BSD_OS 14 | #elif defined(__ANDROID__) 15 | # define CRCPP_ANDROID_OS 16 | #endif 17 | 18 | #if defined(__clang__) 19 | # define CRCPP_CLANG_COMPILER 20 | #elif defined(__GNUC__) || defined(__GNUG__) 21 | # define CRCPP_GCC_COMPILER 22 | #elif defined(_MSC_VER) 23 | # define CRCPP_MSVC_COMPILER 24 | #endif 25 | 26 | #if !defined(NDEBUG) || defined(_DEBUG) 27 | # define CRCPP_DEBUG_MODE 28 | #endif 29 | 30 | #if defined(CRCPP_WIN_OS) 31 | # if defined(CRCPP_EXPORT_API) 32 | # define CRCPP_API __declspec(dllexport) 33 | # elif defined(CRCPP_IMPORT_API) 34 | # define CRCPP_API __declspec(dllimport) 35 | # endif 36 | #elif (defined(CRCPP_EXPORT_API) || defined(CRCPP_IMPORT_API)) && __has_cpp_attribute(gnu::visibility) 37 | # define CRCPP_API __attribute__((visibility("default"))) 38 | #endif 39 | 40 | #if !defined(CRCPP_API) 41 | # define CRCPP_API 42 | #endif 43 | 44 | #include 45 | 46 | #if defined(_LIBCPP_VERSION) 47 | # define CRCPP_LIBCPP_LIB 48 | #endif 49 | 50 | #endif // PLATFORM_DEFS_H 51 | -------------------------------------------------------------------------------- /include/concurrencpp/results/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RESULT_CONSTS_H 2 | #define CONCURRENCPP_RESULT_CONSTS_H 3 | 4 | namespace concurrencpp::details::consts { 5 | /* 6 | * result_promise 7 | */ 8 | 9 | inline const char* k_result_promise_set_result_error_msg = "concurrencpp::result_promise::set_result() - empty result_promise."; 10 | 11 | inline const char* k_result_promise_set_exception_error_msg = "concurrencpp::result_promise::set_exception() - empty result_promise."; 12 | 13 | inline const char* k_result_promise_set_exception_null_exception_error_msg = 14 | "concurrencpp::result_promise::set_exception() - exception pointer is null."; 15 | 16 | inline const char* k_result_promise_set_from_function_error_msg = "concurrencpp::result_promise::set_from_function() - empty result_promise."; 17 | 18 | inline const char* k_result_promise_get_result_error_msg = "concurrencpp::result_promise::get_result() - empty result_promise."; 19 | 20 | inline const char* k_result_promise_get_result_already_retrieved_error_msg = 21 | "concurrencpp::result_promise::get_result() - result was already retrieved."; 22 | 23 | /* 24 | * result 25 | */ 26 | 27 | inline const char* k_result_status_error_msg = "concurrencpp::result::status() - result is empty."; 28 | 29 | inline const char* k_result_get_error_msg = "concurrencpp::result::get() - result is empty."; 30 | 31 | inline const char* k_result_wait_error_msg = "concurrencpp::result::wait() - result is empty."; 32 | 33 | inline const char* k_result_wait_for_error_msg = "concurrencpp::result::wait_for() - result is empty."; 34 | 35 | inline const char* k_result_wait_until_error_msg = "concurrencpp::result::wait_until() - result is empty."; 36 | 37 | inline const char* k_result_operator_co_await_error_msg = "concurrencpp::result::operator co_await() - result is empty."; 38 | 39 | inline const char* k_result_resolve_error_msg = "concurrencpp::result::resolve() - result is empty."; 40 | 41 | inline const char* k_executor_exception_error_msg = 42 | "concurrencpp::concurrencpp::result - an exception was thrown while trying to enqueue result continuation."; 43 | 44 | inline const char* k_broken_task_exception_error_msg = "concurrencpp::result - associated task was interrupted abnormally"; 45 | 46 | /* 47 | * when_xxx 48 | */ 49 | 50 | inline const char* k_make_exceptional_result_exception_null_error_msg = "concurrencpp::make_exception_result() - given exception_ptr is null."; 51 | 52 | inline const char* k_when_all_empty_result_error_msg = "concurrencpp::when_all() - one of the result objects is empty."; 53 | 54 | inline const char* k_when_all_null_resume_executor_error_msg = "concurrencpp::when_all() - given resume_executor is null."; 55 | 56 | inline const char* k_when_any_empty_result_error_msg = "concurrencpp::when_any() - one of the result objects is empty."; 57 | 58 | inline const char* k_when_any_empty_range_error_msg = "concurrencpp::when_any() - given range contains no elements."; 59 | 60 | inline const char* k_when_any_null_resume_executor_error_msg = "concurrencpp::when_any() - given resume_executor is null."; 61 | 62 | /* 63 | * shared_result 64 | */ 65 | 66 | inline const char* k_shared_result_status_error_msg = "concurrencpp::shared_result::status() - result is empty."; 67 | 68 | inline const char* k_shared_result_get_error_msg = "concurrencpp::shared_result::get() - result is empty."; 69 | 70 | inline const char* k_shared_result_wait_error_msg = "concurrencpp::shared_result::wait() - result is empty."; 71 | 72 | inline const char* k_shared_result_wait_for_error_msg = "concurrencpp::shared_result::wait_for() - result is empty."; 73 | 74 | inline const char* k_shared_result_wait_until_error_msg = "concurrencpp::shared_result::wait_until() - result is empty."; 75 | 76 | inline const char* k_shared_result_operator_co_await_error_msg = "concurrencpp::shared_result::operator co_await() - result is empty."; 77 | 78 | inline const char* k_shared_result_resolve_error_msg = "concurrencpp::shared_result::resolve() - result is empty."; 79 | 80 | /* 81 | * lazy_result 82 | */ 83 | 84 | inline const char* k_empty_lazy_result_status_err_msg = "concurrencpp::lazy_result::status - result is empty."; 85 | 86 | inline const char* k_empty_lazy_result_operator_co_await_err_msg = "concurrencpp::lazy_result::operator co_await - result is empty."; 87 | 88 | inline const char* k_empty_lazy_result_resolve_err_msg = "concurrencpp::lazy_result::resolve - result is empty."; 89 | 90 | inline const char* k_empty_lazy_result_run_err_msg = "concurrencpp::lazy_result::run - result is empty."; 91 | 92 | /* 93 | * resume_on 94 | */ 95 | 96 | inline const char* k_resume_on_null_exception_err_msg = "concurrencpp::resume_on - given executor is null."; 97 | 98 | /* 99 | * generator 100 | */ 101 | inline const char* k_empty_generator_begin_err_msg = "concurrencpp::generator::begin - generator is empty."; 102 | 103 | /* 104 | * parallel-coroutine 105 | */ 106 | inline const char* k_parallel_coroutine_null_exception_err_msg = "concurrencpp::parallel-coroutine - given executor is null."; 107 | 108 | } // namespace concurrencpp::details::consts 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /include/concurrencpp/results/generator.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_GENERATOR_H 2 | #define CONCURRENCPP_GENERATOR_H 3 | 4 | #include "concurrencpp/results/constants.h" 5 | #include "concurrencpp/results/impl/generator_state.h" 6 | 7 | namespace concurrencpp { 8 | template 9 | class generator { 10 | 11 | public: 12 | using promise_type = details::generator_state; 13 | using iterator = details::generator_iterator; 14 | 15 | static_assert(!std::is_same_v, "concurrencpp::generator - <> can not be void."); 16 | 17 | private: 18 | details::coroutine_handle m_coro_handle; 19 | 20 | public: 21 | generator(details::coroutine_handle handle) noexcept : m_coro_handle(handle) {} 22 | 23 | generator(generator&& rhs) noexcept : m_coro_handle(std::exchange(rhs.m_coro_handle, {})) {} 24 | 25 | ~generator() noexcept { 26 | if (static_cast(m_coro_handle)) { 27 | m_coro_handle.destroy(); 28 | } 29 | } 30 | 31 | generator(const generator& rhs) = delete; 32 | 33 | generator& operator=(generator&& rhs) = delete; 34 | generator& operator=(const generator& rhs) = delete; 35 | 36 | explicit operator bool() const noexcept { 37 | return static_cast(m_coro_handle); 38 | } 39 | 40 | iterator begin() { 41 | if (!static_cast(m_coro_handle)) { 42 | throw errors::empty_generator(details::consts::k_empty_generator_begin_err_msg); 43 | } 44 | 45 | assert(!m_coro_handle.done()); 46 | m_coro_handle.resume(); 47 | 48 | if (m_coro_handle.done()) { 49 | m_coro_handle.promise().throw_if_exception(); 50 | } 51 | 52 | return iterator {m_coro_handle}; 53 | } 54 | 55 | static details::generator_end_iterator end() noexcept { 56 | return {}; 57 | } 58 | }; 59 | } // namespace concurrencpp 60 | 61 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/impl/consumer_context.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_CONSUMER_CONTEXT_H 2 | #define CONCURRENCPP_CONSUMER_CONTEXT_H 3 | 4 | #include "concurrencpp/coroutines/coroutine.h" 5 | #include "concurrencpp/results/result_fwd_declarations.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace concurrencpp::details { 11 | class CRCPP_API await_via_functor { 12 | 13 | private: 14 | coroutine_handle m_caller_handle; 15 | bool* m_interrupted; 16 | 17 | public: 18 | await_via_functor(coroutine_handle caller_handle, bool* interrupted) noexcept; 19 | await_via_functor(await_via_functor&& rhs) noexcept; 20 | ~await_via_functor() noexcept; 21 | 22 | void operator()() noexcept; 23 | }; 24 | 25 | class CRCPP_API when_any_context { 26 | 27 | private: 28 | std::atomic m_status; 29 | coroutine_handle m_coro_handle; 30 | 31 | static const result_state_base* k_processing; 32 | static const result_state_base* k_done_processing; 33 | 34 | public: 35 | when_any_context(coroutine_handle coro_handle) noexcept; 36 | 37 | bool any_result_finished() const noexcept; 38 | bool finish_processing() noexcept; 39 | const result_state_base* completed_result() const noexcept; 40 | 41 | void try_resume(result_state_base& completed_result) noexcept; 42 | bool resume_inline(result_state_base& completed_result) noexcept; 43 | }; 44 | 45 | class CRCPP_API consumer_context { 46 | 47 | private: 48 | enum class consumer_status { idle, await, wait_for, when_any, shared }; 49 | 50 | union storage { 51 | coroutine_handle caller_handle; 52 | std::shared_ptr wait_for_ctx; 53 | std::shared_ptr when_any_ctx; 54 | std::weak_ptr shared_ctx; 55 | 56 | storage() noexcept {} 57 | ~storage() noexcept {} 58 | }; 59 | 60 | private: 61 | consumer_status m_status = consumer_status::idle; 62 | storage m_storage; 63 | 64 | void destroy() noexcept; 65 | 66 | public: 67 | ~consumer_context() noexcept; 68 | 69 | void clear() noexcept; 70 | void resume_consumer(result_state_base& self) const; 71 | 72 | void set_await_handle(coroutine_handle caller_handle) noexcept; 73 | void set_wait_for_context(const std::shared_ptr& wait_ctx) noexcept; 74 | void set_when_any_context(const std::shared_ptr& when_any_ctx) noexcept; 75 | void set_shared_context(const std::shared_ptr& shared_ctx) noexcept; 76 | }; 77 | } // namespace concurrencpp::details 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /include/concurrencpp/results/impl/generator_state.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_GENERATOR_STATE_H 2 | #define CONCURRENCPP_GENERATOR_STATE_H 3 | 4 | #include "concurrencpp/forward_declarations.h" 5 | #include "concurrencpp/coroutines/coroutine.h" 6 | 7 | namespace concurrencpp::details { 8 | template 9 | class generator_state { 10 | 11 | public: 12 | using value_type = std::remove_reference_t; 13 | 14 | private: 15 | value_type* m_value = nullptr; 16 | std::exception_ptr m_exception; 17 | 18 | public: 19 | generator get_return_object() noexcept { 20 | return generator {coroutine_handle>::from_promise(*this)}; 21 | } 22 | 23 | suspend_always initial_suspend() const noexcept { 24 | return {}; 25 | } 26 | 27 | suspend_always final_suspend() const noexcept { 28 | return {}; 29 | } 30 | 31 | suspend_always yield_value(value_type& ref) noexcept { 32 | m_value = std::addressof(ref); 33 | return {}; 34 | } 35 | 36 | suspend_always yield_value(value_type&& ref) noexcept { 37 | m_value = std::addressof(ref); 38 | return {}; 39 | } 40 | 41 | void unhandled_exception() noexcept { 42 | m_exception = std::current_exception(); 43 | } 44 | 45 | void return_void() const noexcept {} 46 | 47 | value_type& value() const noexcept { 48 | assert(m_value != nullptr); 49 | assert(reinterpret_cast(m_value) % alignof(value_type) == 0); 50 | return *m_value; 51 | } 52 | 53 | void throw_if_exception() const { 54 | if (static_cast(m_exception)) { 55 | std::rethrow_exception(m_exception); 56 | } 57 | } 58 | }; 59 | 60 | struct generator_end_iterator {}; 61 | 62 | template 63 | class generator_iterator { 64 | 65 | private: 66 | coroutine_handle> m_coro_handle; 67 | 68 | public: 69 | using value_type = std::remove_reference_t; 70 | using reference = value_type&; 71 | using pointer = value_type*; 72 | using iterator_category = std::input_iterator_tag; 73 | using difference_type = std::ptrdiff_t; 74 | 75 | public: 76 | generator_iterator(coroutine_handle> handle) noexcept : m_coro_handle(handle) { 77 | assert(static_cast(m_coro_handle)); 78 | } 79 | 80 | generator_iterator& operator++() { 81 | assert(static_cast(m_coro_handle)); 82 | assert(!m_coro_handle.done()); 83 | m_coro_handle.resume(); 84 | 85 | if (m_coro_handle.done()) { 86 | m_coro_handle.promise().throw_if_exception(); 87 | } 88 | 89 | return *this; 90 | } 91 | 92 | void operator++(int) { 93 | (void)operator++(); 94 | } 95 | 96 | reference operator*() const noexcept { 97 | assert(static_cast(m_coro_handle)); 98 | return m_coro_handle.promise().value(); 99 | } 100 | 101 | pointer operator->() const noexcept { 102 | assert(static_cast(m_coro_handle)); 103 | return std::addressof(operator*()); 104 | } 105 | 106 | friend bool operator==(const generator_iterator& it0, const generator_iterator& it1) noexcept { 107 | return it0.m_coro_handle == it1.m_coro_handle; 108 | } 109 | 110 | friend bool operator==(const generator_iterator& it, generator_end_iterator) noexcept { 111 | return it.m_coro_handle.done(); 112 | } 113 | 114 | friend bool operator==(generator_end_iterator end_it, const generator_iterator& it) noexcept { 115 | return (it == end_it); 116 | } 117 | 118 | friend bool operator!=(const generator_iterator& it, generator_end_iterator end_it) noexcept { 119 | return !(it == end_it); 120 | } 121 | 122 | friend bool operator!=(generator_end_iterator end_it, const generator_iterator& it) noexcept { 123 | return it != end_it; 124 | } 125 | }; 126 | } // namespace concurrencpp::details 127 | 128 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/impl/lazy_result_state.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_LAZY_RESULT_STATE_H 2 | #define CONCURRENCPP_LAZY_RESULT_STATE_H 3 | 4 | #include "concurrencpp/coroutines/coroutine.h" 5 | #include "concurrencpp/results/impl/producer_context.h" 6 | #include "concurrencpp/results/result_fwd_declarations.h" 7 | 8 | namespace concurrencpp::details { 9 | struct lazy_final_awaiter : public suspend_always { 10 | template 11 | coroutine_handle await_suspend(coroutine_handle handle) noexcept { 12 | return handle.promise().resume_caller(); 13 | } 14 | }; 15 | 16 | class lazy_result_state_base { 17 | 18 | protected: 19 | coroutine_handle m_caller_handle; 20 | 21 | public: 22 | coroutine_handle resume_caller() const noexcept { 23 | return m_caller_handle; 24 | } 25 | 26 | coroutine_handle await(coroutine_handle caller_handle) noexcept { 27 | m_caller_handle = caller_handle; 28 | return coroutine_handle::from_promise(*this); 29 | } 30 | }; 31 | 32 | template 33 | class lazy_result_state : public lazy_result_state_base { 34 | 35 | private: 36 | producer_context m_producer; 37 | 38 | public: 39 | result_status status() const noexcept { 40 | return m_producer.status(); 41 | } 42 | 43 | lazy_result get_return_object() noexcept { 44 | const auto self_handle = coroutine_handle::from_promise(*this); 45 | return lazy_result(self_handle); 46 | } 47 | 48 | void unhandled_exception() noexcept { 49 | m_producer.build_exception(std::current_exception()); 50 | } 51 | 52 | suspend_always initial_suspend() const noexcept { 53 | return {}; 54 | } 55 | 56 | lazy_final_awaiter final_suspend() const noexcept { 57 | return {}; 58 | } 59 | 60 | template 61 | void set_result(argument_types&&... arguments) noexcept(noexcept(type(std::forward(arguments)...))) { 62 | m_producer.build_result(std::forward(arguments)...); 63 | } 64 | 65 | type get() { 66 | return m_producer.get(); 67 | } 68 | }; 69 | } // namespace concurrencpp::details 70 | 71 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/impl/return_value_struct.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RETURN_VALUE_STRUCT_H 2 | #define CONCURRENCPP_RETURN_VALUE_STRUCT_H 3 | 4 | #include 5 | 6 | namespace concurrencpp::details { 7 | template 8 | struct return_value_struct { 9 | template 10 | void return_value(return_type&& value) { 11 | auto self = static_cast(this); 12 | self->set_result(std::forward(value)); 13 | } 14 | }; 15 | 16 | template 17 | struct return_value_struct { 18 | void return_void() noexcept { 19 | auto self = static_cast(this); 20 | self->set_result(); 21 | } 22 | }; 23 | } // namespace concurrencpp::details 24 | 25 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/impl/shared_result_state.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_SHARED_RESULT_STATE_H 2 | #define CONCURRENCPP_SHARED_RESULT_STATE_H 3 | 4 | #include "concurrencpp/platform_defs.h" 5 | #include "concurrencpp/results/impl/result_state.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace concurrencpp::details { 13 | struct shared_result_helper { 14 | template 15 | static consumer_result_state_ptr get_state(result& result) noexcept { 16 | return std::move(result.m_state); 17 | } 18 | }; 19 | 20 | struct CRCPP_API shared_await_context { 21 | shared_await_context* next = nullptr; 22 | coroutine_handle caller_handle; 23 | }; 24 | 25 | class CRCPP_API shared_result_state_base { 26 | 27 | protected: 28 | std::atomic m_status {result_status::idle}; 29 | std::atomic m_awaiters {nullptr}; 30 | std::counting_semaphore<> m_semaphore {0}; 31 | 32 | static shared_await_context* result_ready_constant() noexcept; 33 | 34 | public: 35 | virtual ~shared_result_state_base() noexcept = default; 36 | 37 | virtual void on_result_finished() noexcept = 0; 38 | 39 | result_status status() const noexcept; 40 | 41 | void wait() noexcept; 42 | 43 | bool await(shared_await_context& awaiter) noexcept; 44 | 45 | template 46 | result_status wait_for(std::chrono::duration duration) { 47 | const auto time_point = std::chrono::system_clock::now() + duration; 48 | return wait_until(time_point); 49 | } 50 | 51 | template 52 | result_status wait_until(const std::chrono::time_point& timeout_time) { 53 | while ((status() == result_status::idle) && (clock::now() < timeout_time)) { 54 | const auto res = m_semaphore.try_acquire_until(timeout_time); 55 | (void)res; 56 | } 57 | 58 | return status(); 59 | } 60 | }; 61 | 62 | template 63 | class shared_result_state final : public shared_result_state_base { 64 | 65 | private: 66 | consumer_result_state_ptr m_result_state; 67 | 68 | public: 69 | shared_result_state(consumer_result_state_ptr result_state) noexcept : m_result_state(std::move(result_state)) { 70 | assert(static_cast(m_result_state)); 71 | } 72 | 73 | ~shared_result_state() noexcept { 74 | assert(static_cast(m_result_state)); 75 | m_result_state->try_rewind_consumer(); 76 | m_result_state.reset(); 77 | } 78 | 79 | void share(const std::shared_ptr& self) noexcept { 80 | assert(static_cast(m_result_state)); 81 | m_result_state->share(self); 82 | } 83 | 84 | std::add_lvalue_reference_t get() { 85 | assert(static_cast(m_result_state)); 86 | return m_result_state->get_ref(); 87 | } 88 | 89 | void on_result_finished() noexcept override { 90 | m_status.store(m_result_state->status(), std::memory_order_release); 91 | m_status.notify_all(); 92 | 93 | /* theoretically buggish, practically there's no way 94 | that we'll have more than max(ptrdiff_t) / 2 waiters. 95 | on 64 bits, that's 2^62 waiters, on 32 bits thats 2^30 waiters. 96 | memory will run out before enough tasks could be created to wait this synchronously 97 | */ 98 | m_semaphore.release(m_semaphore.max() / 2); 99 | 100 | auto k_result_ready = result_ready_constant(); 101 | auto awaiters = m_awaiters.exchange(k_result_ready, std::memory_order_acq_rel); 102 | 103 | shared_await_context* current = awaiters; 104 | shared_await_context *prev = nullptr, *next = nullptr; 105 | 106 | while (current != nullptr) { 107 | next = current->next; 108 | current->next = prev; 109 | prev = current; 110 | current = next; 111 | } 112 | 113 | awaiters = prev; 114 | 115 | while (awaiters != nullptr) { 116 | assert(static_cast(awaiters->caller_handle)); 117 | auto caller_handle = awaiters->caller_handle; 118 | awaiters = awaiters->next; 119 | caller_handle(); 120 | } 121 | } 122 | }; 123 | } // namespace concurrencpp::details 124 | 125 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/lazy_result.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_LAZY_RESULT_H 2 | #define CONCURRENCPP_LAZY_RESULT_H 3 | 4 | #include "concurrencpp/results/promises.h" 5 | #include "concurrencpp/results/constants.h" 6 | #include "concurrencpp/results/lazy_result_awaitable.h" 7 | #include "concurrencpp/results/impl/lazy_result_state.h" 8 | 9 | namespace concurrencpp { 10 | template 11 | class lazy_result { 12 | 13 | private: 14 | details::coroutine_handle> m_state; 15 | 16 | void throw_if_empty(const char* err_msg) const { 17 | if (!static_cast(m_state)) { 18 | throw errors::empty_result(err_msg); 19 | } 20 | } 21 | 22 | result run_impl() { 23 | lazy_result self(std::move(*this)); 24 | co_return co_await self; 25 | } 26 | 27 | public: 28 | lazy_result() noexcept = default; 29 | 30 | lazy_result(lazy_result&& rhs) noexcept : m_state(std::exchange(rhs.m_state, {})) {} 31 | 32 | lazy_result(details::coroutine_handle> state) noexcept : m_state(state) {} 33 | 34 | ~lazy_result() noexcept { 35 | if (static_cast(m_state)) { 36 | m_state.destroy(); 37 | } 38 | } 39 | 40 | lazy_result& operator=(lazy_result&& rhs) noexcept { 41 | if (&rhs == this) { 42 | return *this; 43 | } 44 | 45 | if (static_cast(m_state)) { 46 | m_state.destroy(); 47 | } 48 | 49 | m_state = std::exchange(rhs.m_state, {}); 50 | return *this; 51 | } 52 | 53 | explicit operator bool() const noexcept { 54 | return static_cast(m_state); 55 | } 56 | 57 | result_status status() const { 58 | throw_if_empty(details::consts::k_empty_lazy_result_status_err_msg); 59 | return m_state.promise().status(); 60 | } 61 | 62 | auto operator co_await() { 63 | throw_if_empty(details::consts::k_empty_lazy_result_operator_co_await_err_msg); 64 | return lazy_awaitable {std::exchange(m_state, {})}; 65 | } 66 | 67 | auto resolve() { 68 | throw_if_empty(details::consts::k_empty_lazy_result_resolve_err_msg); 69 | return lazy_resolve_awaitable {std::exchange(m_state, {})}; 70 | } 71 | 72 | result run() { 73 | throw_if_empty(details::consts::k_empty_lazy_result_run_err_msg); 74 | return run_impl(); 75 | } 76 | }; 77 | } // namespace concurrencpp 78 | 79 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/lazy_result_awaitable.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_LAZY_RESULT_AWAITABLE_H 2 | #define CONCURRENCPP_LAZY_RESULT_AWAITABLE_H 3 | 4 | #include "concurrencpp/results/impl/lazy_result_state.h" 5 | 6 | namespace concurrencpp { 7 | template 8 | class lazy_awaitable { 9 | 10 | private: 11 | const details::coroutine_handle> m_state; 12 | 13 | public: 14 | lazy_awaitable(details::coroutine_handle> state) noexcept : m_state(state) { 15 | assert(static_cast(state)); 16 | } 17 | 18 | lazy_awaitable(const lazy_awaitable&) = delete; 19 | lazy_awaitable(lazy_awaitable&&) = delete; 20 | 21 | ~lazy_awaitable() noexcept { 22 | auto state = m_state; 23 | state.destroy(); 24 | } 25 | 26 | bool await_ready() const noexcept { 27 | return m_state.done(); 28 | } 29 | 30 | details::coroutine_handle await_suspend(details::coroutine_handle caller_handle) noexcept { 31 | return m_state.promise().await(caller_handle); 32 | } 33 | 34 | type await_resume() { 35 | return m_state.promise().get(); 36 | } 37 | }; 38 | 39 | template 40 | class lazy_resolve_awaitable { 41 | 42 | private: 43 | details::coroutine_handle> m_state; 44 | 45 | public: 46 | lazy_resolve_awaitable(details::coroutine_handle> state) noexcept : m_state(state) { 47 | assert(static_cast(state)); 48 | } 49 | 50 | lazy_resolve_awaitable(const lazy_resolve_awaitable&) = delete; 51 | lazy_resolve_awaitable(lazy_resolve_awaitable&&) = delete; 52 | 53 | ~lazy_resolve_awaitable() noexcept { 54 | if (static_cast(m_state)) { 55 | m_state.destroy(); 56 | } 57 | } 58 | 59 | bool await_ready() const noexcept { 60 | return m_state.done(); 61 | } 62 | 63 | details::coroutine_handle await_suspend(details::coroutine_handle caller_handle) noexcept { 64 | return m_state.promise().await(caller_handle); 65 | } 66 | 67 | lazy_result await_resume() noexcept { 68 | return {std::exchange(m_state, {})}; 69 | } 70 | }; 71 | } // namespace concurrencpp 72 | 73 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/make_result.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_MAKE_RESULT_H 2 | #define CONCURRENCPP_MAKE_RESULT_H 3 | 4 | #include "concurrencpp/results/result.h" 5 | 6 | namespace concurrencpp { 7 | template 8 | result make_ready_result(argument_types&&... arguments) { 9 | static_assert(std::is_constructible_v || std::is_same_v, 10 | "concurrencpp::make_ready_result - <> is not constructible from <"); 11 | 12 | static_assert(std::is_same_v ? (sizeof...(argument_types) == 0) : true, 13 | "concurrencpp::make_ready_result - this overload does not accept any argument."); 14 | 15 | details::producer_result_state_ptr promise(new details::result_state()); 16 | details::consumer_result_state_ptr state_ptr(promise.get()); 17 | 18 | promise->set_result(std::forward(arguments)...); 19 | promise.reset(); // publish the result; 20 | 21 | return {std::move(state_ptr)}; 22 | } 23 | 24 | template 25 | result make_exceptional_result(std::exception_ptr exception_ptr) { 26 | if (!static_cast(exception_ptr)) { 27 | throw std::invalid_argument(details::consts::k_make_exceptional_result_exception_null_error_msg); 28 | } 29 | 30 | details::producer_result_state_ptr promise(new details::result_state()); 31 | details::consumer_result_state_ptr state_ptr(promise.get()); 32 | 33 | promise->set_exception(exception_ptr); 34 | promise.reset(); // publish the result; 35 | 36 | return {std::move(state_ptr)}; 37 | } 38 | 39 | template 40 | result make_exceptional_result(exception_type exception) { 41 | return make_exceptional_result(std::make_exception_ptr(exception)); 42 | } 43 | } // namespace concurrencpp 44 | 45 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/result_awaitable.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RESULT_AWAITABLE_H 2 | #define CONCURRENCPP_RESULT_AWAITABLE_H 3 | 4 | #include "concurrencpp/coroutines/coroutine.h" 5 | #include "concurrencpp/results/impl/result_state.h" 6 | 7 | namespace concurrencpp::details { 8 | template 9 | class awaitable_base : public suspend_always { 10 | protected: 11 | consumer_result_state_ptr m_state; 12 | 13 | public: 14 | awaitable_base(consumer_result_state_ptr state) noexcept : m_state(std::move(state)) {} 15 | 16 | awaitable_base(const awaitable_base&) = delete; 17 | awaitable_base(awaitable_base&&) = delete; 18 | }; 19 | } // namespace concurrencpp::details 20 | 21 | namespace concurrencpp { 22 | template 23 | class awaitable : public details::awaitable_base { 24 | 25 | public: 26 | awaitable(details::consumer_result_state_ptr state) noexcept : details::awaitable_base(std::move(state)) {} 27 | 28 | bool await_suspend(details::coroutine_handle caller_handle) noexcept { 29 | assert(static_cast(this->m_state)); 30 | return this->m_state->await(caller_handle); 31 | } 32 | 33 | type await_resume() { 34 | details::joined_consumer_result_state_ptr state(this->m_state.release()); 35 | return state->get(); 36 | } 37 | }; 38 | 39 | template 40 | class resolve_awaitable : public details::awaitable_base { 41 | 42 | public: 43 | resolve_awaitable(details::consumer_result_state_ptr state) noexcept : details::awaitable_base(std::move(state)) {} 44 | 45 | resolve_awaitable(resolve_awaitable&&) noexcept = delete; 46 | resolve_awaitable(const resolve_awaitable&) noexcept = delete; 47 | 48 | bool await_suspend(details::coroutine_handle caller_handle) noexcept { 49 | assert(static_cast(this->m_state)); 50 | return this->m_state->await(caller_handle); 51 | } 52 | 53 | result await_resume() { 54 | return result(std::move(this->m_state)); 55 | } 56 | }; 57 | } // namespace concurrencpp 58 | 59 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/results/result_fwd_declarations.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RESULT_FWD_DECLARATIONS_H 2 | #define CONCURRENCPP_RESULT_FWD_DECLARATIONS_H 3 | 4 | #include "concurrencpp/forward_declarations.h" 5 | #include "concurrencpp/coroutines/coroutine.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace concurrencpp { 11 | template 12 | class result; 13 | 14 | template 15 | class shared_result; 16 | 17 | template 18 | class lazy_result; 19 | 20 | template 21 | class result_promise; 22 | 23 | template 24 | class awaitable; 25 | 26 | template 27 | class resolve_awaitable; 28 | 29 | struct executor_tag {}; 30 | 31 | struct null_result {}; 32 | 33 | enum class result_status { idle, value, exception }; 34 | } // namespace concurrencpp 35 | 36 | namespace concurrencpp::details { 37 | class result_state_base; 38 | 39 | template 40 | class result_state; 41 | 42 | class shared_result_state_base; 43 | 44 | template 45 | class shared_result_state; 46 | 47 | template 48 | class lazy_result_state; 49 | 50 | class when_result_helper; 51 | struct shared_result_helper; 52 | } // namespace concurrencpp::details 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/concurrencpp/results/resume_on.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RESUME_ON_H 2 | #define CONCURRENCPP_RESUME_ON_H 3 | 4 | #include "concurrencpp/executors/executor.h" 5 | #include "concurrencpp/results/impl/consumer_context.h" 6 | 7 | #include 8 | 9 | namespace concurrencpp::details { 10 | template 11 | class resume_on_awaitable : public suspend_always { 12 | 13 | private: 14 | executor_type& m_executor; 15 | bool m_interrupted = false; 16 | 17 | public: 18 | resume_on_awaitable(executor_type& executor) noexcept : m_executor(executor) {} 19 | 20 | resume_on_awaitable(const resume_on_awaitable&) = delete; 21 | resume_on_awaitable(resume_on_awaitable&&) = delete; 22 | 23 | resume_on_awaitable& operator=(const resume_on_awaitable&) = delete; 24 | resume_on_awaitable& operator=(resume_on_awaitable&&) = delete; 25 | 26 | void await_suspend(coroutine_handle handle) { 27 | try { 28 | m_executor.post(await_via_functor {handle, &m_interrupted}); 29 | } catch (...) { 30 | // the exception caused the enqeueud task to be broken and resumed with an interrupt, no need to do anything here. 31 | } 32 | } 33 | 34 | void await_resume() const { 35 | if (m_interrupted) { 36 | throw errors::broken_task(consts::k_broken_task_exception_error_msg); 37 | } 38 | } 39 | }; 40 | } // namespace concurrencpp::details 41 | 42 | namespace concurrencpp { 43 | template 44 | auto resume_on(std::shared_ptr executor) { 45 | static_assert(std::is_base_of_v, 46 | "concurrencpp::resume_on() - given executor does not derive from concurrencpp::executor"); 47 | 48 | if (!static_cast(executor)) { 49 | throw std::invalid_argument(details::consts::k_resume_on_null_exception_err_msg); 50 | } 51 | 52 | return details::resume_on_awaitable(*executor); 53 | } 54 | 55 | template 56 | auto resume_on(executor_type& executor) noexcept { 57 | return details::resume_on_awaitable(executor); 58 | } 59 | } // namespace concurrencpp 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/concurrencpp/results/shared_result.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_SHARED_RESULT_H 2 | #define CONCURRENCPP_SHARED_RESULT_H 3 | 4 | #include "concurrencpp/results/result.h" 5 | #include "concurrencpp/results/shared_result_awaitable.h" 6 | #include "concurrencpp/results/impl/shared_result_state.h" 7 | 8 | namespace concurrencpp { 9 | template 10 | class shared_result { 11 | 12 | private: 13 | std::shared_ptr> m_state; 14 | 15 | void throw_if_empty(const char* message) const { 16 | if (!static_cast(m_state)) { 17 | throw errors::empty_result(message); 18 | } 19 | } 20 | 21 | public: 22 | shared_result() noexcept = default; 23 | ~shared_result() noexcept = default; 24 | 25 | shared_result(std::shared_ptr> state) noexcept : m_state(std::move(state)) {} 26 | 27 | shared_result(result rhs) { 28 | if (!static_cast(rhs)) { 29 | return; 30 | } 31 | 32 | auto result_state = details::shared_result_helper::get_state(rhs); 33 | m_state = std::make_shared>(std::move(result_state)); 34 | m_state->share(std::static_pointer_cast(m_state)); 35 | } 36 | 37 | shared_result(const shared_result& rhs) noexcept = default; 38 | shared_result(shared_result&& rhs) noexcept = default; 39 | 40 | shared_result& operator=(const shared_result& rhs) noexcept { 41 | if (this != &rhs && m_state != rhs.m_state) { 42 | m_state = rhs.m_state; 43 | } 44 | 45 | return *this; 46 | } 47 | 48 | shared_result& operator=(shared_result&& rhs) noexcept { 49 | if (this != &rhs && m_state != rhs.m_state) { 50 | m_state = std::move(rhs.m_state); 51 | } 52 | 53 | return *this; 54 | } 55 | 56 | operator bool() const noexcept { 57 | return static_cast(m_state.get()); 58 | } 59 | 60 | result_status status() const { 61 | throw_if_empty(details::consts::k_shared_result_status_error_msg); 62 | return m_state->status(); 63 | } 64 | 65 | void wait() { 66 | throw_if_empty(details::consts::k_shared_result_wait_error_msg); 67 | m_state->wait(); 68 | } 69 | 70 | template 71 | result_status wait_for(std::chrono::duration duration) { 72 | throw_if_empty(details::consts::k_shared_result_wait_for_error_msg); 73 | return m_state->wait_for(duration); 74 | } 75 | 76 | template 77 | result_status wait_until(std::chrono::time_point timeout_time) { 78 | throw_if_empty(details::consts::k_shared_result_wait_until_error_msg); 79 | return m_state->wait_until(timeout_time); 80 | } 81 | 82 | std::add_lvalue_reference_t get() { 83 | throw_if_empty(details::consts::k_shared_result_get_error_msg); 84 | m_state->wait(); 85 | return m_state->get(); 86 | } 87 | 88 | auto operator co_await() { 89 | throw_if_empty(details::consts::k_shared_result_operator_co_await_error_msg); 90 | return shared_awaitable {m_state}; 91 | } 92 | 93 | auto resolve() { 94 | throw_if_empty(details::consts::k_shared_result_resolve_error_msg); 95 | return shared_resolve_awaitable {m_state}; 96 | } 97 | }; 98 | } // namespace concurrencpp 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /include/concurrencpp/results/shared_result_awaitable.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_SHARED_RESULT_AWAITABLE_H 2 | #define CONCURRENCPP_SHARED_RESULT_AWAITABLE_H 3 | 4 | #include "concurrencpp/results/impl/shared_result_state.h" 5 | 6 | namespace concurrencpp::details { 7 | template 8 | class shared_awaitable_base : public suspend_always { 9 | 10 | protected: 11 | std::shared_ptr> m_state; 12 | 13 | public: 14 | shared_awaitable_base(const std::shared_ptr>& state) noexcept : m_state(state) {} 15 | 16 | shared_awaitable_base(const shared_awaitable_base&) = delete; 17 | shared_awaitable_base(shared_awaitable_base&&) = delete; 18 | }; 19 | } // namespace concurrencpp::details 20 | 21 | namespace concurrencpp { 22 | template 23 | class shared_awaitable : public details::shared_awaitable_base { 24 | 25 | private: 26 | details::shared_await_context m_await_ctx; 27 | 28 | public: 29 | shared_awaitable(const std::shared_ptr>& state) noexcept : 30 | details::shared_awaitable_base(state) {} 31 | 32 | bool await_suspend(details::coroutine_handle caller_handle) noexcept { 33 | assert(static_cast(this->m_state)); 34 | this->m_await_ctx.caller_handle = caller_handle; 35 | return this->m_state->await(m_await_ctx); 36 | } 37 | 38 | std::add_lvalue_reference_t await_resume() { 39 | return this->m_state->get(); 40 | } 41 | }; 42 | 43 | template 44 | class shared_resolve_awaitable : public details::shared_awaitable_base { 45 | 46 | private: 47 | details::shared_await_context m_await_ctx; 48 | 49 | public: 50 | shared_resolve_awaitable(const std::shared_ptr>& state) noexcept : 51 | details::shared_awaitable_base(state) {} 52 | 53 | bool await_suspend(details::coroutine_handle caller_handle) noexcept { 54 | assert(static_cast(this->m_state)); 55 | this->m_await_ctx.caller_handle = caller_handle; 56 | return this->m_state->await(m_await_ctx); 57 | } 58 | 59 | shared_result await_resume() { 60 | return shared_result(std::move(this->m_state)); 61 | } 62 | }; 63 | } // namespace concurrencpp 64 | 65 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/runtime/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_CONSTANTS_H 2 | #define CONCURRENCPP_CONSTANTS_H 3 | 4 | #include 5 | 6 | namespace concurrencpp::details::consts { 7 | constexpr static size_t k_cpu_threadpool_worker_count_factor = 1; 8 | constexpr static size_t k_background_threadpool_worker_count_factor = 4; 9 | constexpr static size_t k_max_threadpool_worker_waiting_time_sec = 2 * 60; 10 | constexpr static size_t k_default_number_of_cores = 8; 11 | constexpr static size_t k_max_timer_queue_worker_waiting_time_sec = 2 * 60; 12 | 13 | constexpr static unsigned int k_concurrencpp_version_major = 0; 14 | constexpr static unsigned int k_concurrencpp_version_minor = 1; 15 | constexpr static unsigned int k_concurrencpp_version_revision = 7; 16 | } // namespace concurrencpp::details::consts 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/concurrencpp/runtime/runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RUNTIME_H 2 | #define CONCURRENCPP_RUNTIME_H 3 | 4 | #include "concurrencpp/runtime/constants.h" 5 | #include "concurrencpp/forward_declarations.h" 6 | #include "concurrencpp/platform_defs.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace concurrencpp::details { 15 | class CRCPP_API executor_collection { 16 | 17 | private: 18 | std::mutex m_lock; 19 | std::vector> m_executors; 20 | 21 | public: 22 | void register_executor(std::shared_ptr executor); 23 | void shutdown_all(); 24 | }; 25 | } // namespace concurrencpp::details 26 | 27 | namespace concurrencpp { 28 | struct CRCPP_API runtime_options { 29 | size_t max_cpu_threads; 30 | std::chrono::milliseconds max_thread_pool_executor_waiting_time; 31 | 32 | size_t max_background_threads; 33 | std::chrono::milliseconds max_background_executor_waiting_time; 34 | 35 | std::chrono::milliseconds max_timer_queue_waiting_time; 36 | 37 | std::function thread_started_callback; 38 | std::function thread_terminated_callback; 39 | 40 | runtime_options() noexcept; 41 | 42 | runtime_options(const runtime_options&) noexcept = default; 43 | runtime_options& operator=(const runtime_options&) noexcept = default; 44 | }; 45 | 46 | class CRCPP_API runtime { 47 | 48 | private: 49 | std::shared_ptr m_inline_executor; 50 | std::shared_ptr m_thread_pool_executor; 51 | std::shared_ptr m_background_executor; 52 | std::shared_ptr m_thread_executor; 53 | 54 | details::executor_collection m_registered_executors; 55 | 56 | std::shared_ptr m_timer_queue; 57 | 58 | public: 59 | runtime(); 60 | runtime(const concurrencpp::runtime_options& options); 61 | 62 | ~runtime() noexcept; 63 | 64 | std::shared_ptr timer_queue() const noexcept; 65 | 66 | std::shared_ptr inline_executor() const noexcept; 67 | std::shared_ptr thread_pool_executor() const noexcept; 68 | std::shared_ptr background_executor() const noexcept; 69 | std::shared_ptr thread_executor() const noexcept; 70 | 71 | std::shared_ptr make_worker_thread_executor(); 72 | std::shared_ptr make_manual_executor(); 73 | 74 | static std::tuple version() noexcept; 75 | 76 | template 77 | std::shared_ptr make_executor(argument_types&&... arguments) { 78 | static_assert( 79 | std::is_base_of_v, 80 | "concurrencpp::runtime::make_executor - <> is not a derived class of concurrencpp::executor."); 81 | 82 | static_assert(std::is_constructible_v, 83 | "concurrencpp::runtime::make_executor - can not build <> from <>."); 84 | 85 | static_assert(!std::is_abstract_v, 86 | "concurrencpp::runtime::make_executor - <> is an abstract class."); 87 | 88 | auto executor = std::make_shared(std::forward(arguments)...); 89 | m_registered_executors.register_executor(executor); 90 | return executor; 91 | } 92 | }; 93 | } // namespace concurrencpp 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /include/concurrencpp/threads/async_condition_variable.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_ASYNC_CONDITION_VARIABLE_H 2 | #define CONCURRENCPP_ASYNC_CONDITION_VARIABLE_H 3 | 4 | #include "concurrencpp/utils/slist.h" 5 | #include "concurrencpp/threads/async_lock.h" 6 | #include "concurrencpp/results/lazy_result.h" 7 | #include "concurrencpp/coroutines/coroutine.h" 8 | #include "concurrencpp/forward_declarations.h" 9 | 10 | namespace concurrencpp::details { 11 | class CRCPP_API cv_awaiter { 12 | private: 13 | async_condition_variable& m_parent; 14 | scoped_async_lock& m_lock; 15 | coroutine_handle m_caller_handle; 16 | 17 | public: 18 | cv_awaiter* next = nullptr; 19 | 20 | cv_awaiter(async_condition_variable& parent, scoped_async_lock& lock) noexcept; 21 | 22 | constexpr bool await_ready() const noexcept { 23 | return false; 24 | } 25 | 26 | void await_suspend(details::coroutine_handle caller_handle); 27 | void await_resume() const noexcept {} 28 | void resume() noexcept; 29 | }; 30 | } // namespace concurrencpp::details 31 | 32 | namespace concurrencpp { 33 | class CRCPP_API async_condition_variable { 34 | 35 | friend details::cv_awaiter; 36 | 37 | private: 38 | template 39 | lazy_result await_impl(std::shared_ptr resume_executor, scoped_async_lock& lock, predicate_type pred) { 40 | while (true) { 41 | assert(lock.owns_lock()); 42 | if (pred()) { 43 | break; 44 | } 45 | 46 | co_await await_impl(resume_executor, lock); 47 | } 48 | } 49 | 50 | private: 51 | std::mutex m_lock; 52 | details::slist m_awaiters; 53 | 54 | static void verify_await_params(const std::shared_ptr& resume_executor, const scoped_async_lock& lock); 55 | 56 | lazy_result await_impl(std::shared_ptr resume_executor, scoped_async_lock& lock); 57 | 58 | public: 59 | async_condition_variable() noexcept = default; 60 | ~async_condition_variable() noexcept; 61 | 62 | async_condition_variable(const async_condition_variable&) noexcept = delete; 63 | async_condition_variable(async_condition_variable&&) noexcept = delete; 64 | 65 | lazy_result await(std::shared_ptr resume_executor, scoped_async_lock& lock); 66 | 67 | template 68 | lazy_result await(std::shared_ptr resume_executor, scoped_async_lock& lock, predicate_type pred) { 69 | static_assert( 70 | std::is_invocable_r_v, 71 | "concurrencpp::async_condition_variable::await - given predicate isn't invocable with no arguments, or does not return a type which is or convertible to bool."); 72 | 73 | verify_await_params(resume_executor, lock); 74 | return await_impl(std::move(resume_executor), lock, pred); 75 | } 76 | 77 | void notify_one(); 78 | void notify_all(); 79 | }; 80 | } // namespace concurrencpp 81 | 82 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/threads/async_lock.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_ASYNC_LOCK_H 2 | #define CONCURRENCPP_ASYNC_LOCK_H 3 | 4 | #include "concurrencpp/utils/slist.h" 5 | #include "concurrencpp/platform_defs.h" 6 | #include "concurrencpp/executors/executor.h" 7 | #include "concurrencpp/results/lazy_result.h" 8 | #include "concurrencpp/forward_declarations.h" 9 | 10 | namespace concurrencpp::details { 11 | class async_lock_awaiter { 12 | 13 | friend class concurrencpp::async_lock; 14 | 15 | private: 16 | async_lock& m_parent; 17 | std::unique_lock m_lock; 18 | coroutine_handle m_resume_handle; 19 | 20 | public: 21 | async_lock_awaiter* next = nullptr; 22 | 23 | public: 24 | async_lock_awaiter(async_lock& parent, std::unique_lock& lock) noexcept; 25 | 26 | constexpr bool await_ready() const noexcept { 27 | return false; 28 | } 29 | 30 | void await_suspend(coroutine_handle handle); 31 | 32 | constexpr void await_resume() const noexcept {} 33 | 34 | void retry() noexcept; 35 | }; 36 | } // namespace concurrencpp::details 37 | 38 | namespace concurrencpp { 39 | class scoped_async_lock; 40 | 41 | class CRCPP_API async_lock { 42 | 43 | friend class scoped_async_lock; 44 | friend class details::async_lock_awaiter; 45 | 46 | private: 47 | std::mutex m_awaiter_lock; 48 | details::slist m_awaiters; 49 | bool m_locked = false; 50 | 51 | #ifdef CRCPP_DEBUG_MODE 52 | std::atomic_intptr_t m_thread_count_in_critical_section {0}; 53 | #endif 54 | 55 | lazy_result lock_impl(std::shared_ptr resume_executor, bool with_raii_guard); 56 | 57 | public: 58 | ~async_lock() noexcept; 59 | 60 | lazy_result lock(std::shared_ptr resume_executor); 61 | lazy_result try_lock(); 62 | void unlock(); 63 | }; 64 | 65 | class CRCPP_API scoped_async_lock { 66 | 67 | private: 68 | async_lock* m_lock = nullptr; 69 | bool m_owns = false; 70 | 71 | public: 72 | scoped_async_lock() noexcept = default; 73 | scoped_async_lock(scoped_async_lock&& rhs) noexcept; 74 | 75 | scoped_async_lock(async_lock& lock, std::defer_lock_t) noexcept; 76 | scoped_async_lock(async_lock& lock, std::adopt_lock_t) noexcept; 77 | 78 | ~scoped_async_lock() noexcept; 79 | 80 | lazy_result lock(std::shared_ptr resume_executor); 81 | lazy_result try_lock(); 82 | void unlock(); 83 | 84 | bool owns_lock() const noexcept; 85 | explicit operator bool() const noexcept; 86 | 87 | void swap(scoped_async_lock& rhs) noexcept; 88 | async_lock* release() noexcept; 89 | async_lock* mutex() const noexcept; 90 | }; 91 | } // namespace concurrencpp 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /include/concurrencpp/threads/cache_line.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_CACHE_LINE_H 2 | #define CONCURRENCPP_CACHE_LINE_H 3 | 4 | #include "concurrencpp/platform_defs.h" 5 | 6 | #include 7 | 8 | #if !defined(CRCPP_MAC_OS) && defined(__cpp_lib_hardware_interference_size) 9 | # if defined(CRCPP_GCC_COMPILER) 10 | # pragma GCC diagnostic push 11 | # pragma GCC diagnostic ignored "-Winterference-size" 12 | # endif 13 | constexpr inline std::size_t CRCPP_CACHE_LINE_ALIGNMENT = std::hardware_destructive_interference_size; 14 | # if defined(CRCPP_GCC_COMPILER) 15 | # pragma GCC diagnostic pop 16 | # endif 17 | #else 18 | constexpr inline std::size_t CRCPP_CACHE_LINE_ALIGNMENT = 64; 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/concurrencpp/threads/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_THREAD_CONSTS_H 2 | #define CONCURRENCPP_THREAD_CONSTS_H 3 | 4 | namespace concurrencpp::details::consts { 5 | inline const char* k_async_lock_null_resume_executor_err_msg = "concurrencpp::async_lock::lock() - given resume executor is null."; 6 | inline const char* k_async_lock_unlock_invalid_lock_err_msg = "concurrencpp::async_lock::unlock() - trying to unlock an unowned lock."; 7 | 8 | inline const char* k_scoped_async_lock_null_resume_executor_err_msg = "concurrencpp::scoped_async_lock::lock() - given resume executor is null."; 9 | 10 | inline const char* k_scoped_async_lock_lock_deadlock_err_msg = "concurrencpp::scoped_async_lock::lock() - *this is already locked."; 11 | 12 | inline const char* k_scoped_async_lock_lock_no_mutex_err_msg = 13 | "concurrencpp::scoped_async_lock::lock() - *this doesn't reference any async_lock."; 14 | 15 | inline const char* k_scoped_async_lock_try_lock_deadlock_err_msg = "concurrencpp::scoped_async_lock::try_lock() - *this is already locked."; 16 | 17 | inline const char* k_scoped_async_lock_try_lock_no_mutex_err_msg = 18 | "concurrencpp::scoped_async_lock::try_lock() - *this doesn't reference any async_lock."; 19 | 20 | inline const char* k_scoped_async_lock_unlock_invalid_lock_err_msg = 21 | "concurrencpp::scoped_async_lock::unlock() - trying to unlock an unowned lock."; 22 | 23 | inline const char* k_async_condition_variable_await_invalid_resume_executor_err_msg = 24 | "concurrencpp::async_condition_variable::await() - resume_executor is null."; 25 | 26 | inline const char* k_async_condition_variable_await_lock_unlocked_err_msg = 27 | "concurrencpp::async_condition_variable::await() - lock is unlocked."; 28 | 29 | } // namespace concurrencpp::details::consts 30 | 31 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/threads/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_THREAD_H 2 | #define CONCURRENCPP_THREAD_H 3 | 4 | #include "concurrencpp/platform_defs.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace concurrencpp::details { 11 | class CRCPP_API thread { 12 | 13 | private: 14 | std::thread m_thread; 15 | 16 | static void set_name(std::string_view name) noexcept; 17 | 18 | public: 19 | thread() noexcept = default; 20 | thread(thread&&) noexcept = default; 21 | 22 | template 23 | thread(std::string name, 24 | callable_type&& callable, 25 | std::function thread_started_callback, 26 | std::function thread_terminated_callback) { 27 | m_thread = std::thread([name = std::move(name), 28 | callable = std::forward(callable), 29 | thread_started_callback = std::move(thread_started_callback), 30 | thread_terminated_callback = std::move(thread_terminated_callback)]() mutable { 31 | set_name(name); 32 | 33 | if (static_cast(thread_started_callback)) { 34 | thread_started_callback(name); 35 | } 36 | 37 | callable(); 38 | 39 | if (static_cast(thread_terminated_callback)) { 40 | thread_terminated_callback(name); 41 | } 42 | }); 43 | } 44 | 45 | thread& operator=(thread&& rhs) noexcept = default; 46 | 47 | std::thread::id get_id() const noexcept; 48 | 49 | static std::uintptr_t get_current_virtual_id() noexcept; 50 | 51 | bool joinable() const noexcept; 52 | void join(); 53 | 54 | static size_t hardware_concurrency() noexcept; 55 | }; 56 | } // namespace concurrencpp::details 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /include/concurrencpp/timers/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_TIMER_CONSTS_H 2 | #define CONCURRENCPP_TIMER_CONSTS_H 3 | 4 | namespace concurrencpp::details::consts { 5 | inline const char* k_timer_empty_get_due_time_err_msg = "concurrencpp::timer::get_due_time() - timer is empty."; 6 | inline const char* k_timer_empty_get_frequency_err_msg = "concurrencpp::timer::get_frequency() - timer is empty."; 7 | inline const char* k_timer_empty_get_executor_err_msg = "concurrencpp::timer::get_executor() - timer is empty."; 8 | inline const char* k_timer_empty_get_timer_queue_err_msg = "concurrencpp::timer::get_timer_queue() - timer is empty."; 9 | inline const char* k_timer_empty_set_frequency_err_msg = "concurrencpp::timer::set_frequency() - timer is empty."; 10 | 11 | inline const char* k_timer_queue_make_timer_executor_null_err_msg = "concurrencpp::timer_queue::make_timer() - executor is null."; 12 | inline const char* k_timer_queue_make_oneshot_timer_executor_null_err_msg = 13 | "concurrencpp::timer_queue::make_one_shot_timer() - executor is null."; 14 | inline const char* k_timer_queue_make_delay_object_executor_null_err_msg = "concurrencpp::timer_queue::make_delay_object() - executor is null."; 15 | inline const char* k_timer_queue_shutdown_err_msg = "concurrencpp::timer_queue has been shut down."; 16 | } // namespace concurrencpp::details::consts 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/concurrencpp/timers/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_TIMER_H 2 | #define CONCURRENCPP_TIMER_H 3 | 4 | #include "concurrencpp/forward_declarations.h" 5 | #include "concurrencpp/platform_defs.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace concurrencpp::details { 12 | class CRCPP_API timer_state_base : public std::enable_shared_from_this { 13 | 14 | public: 15 | using clock_type = std::chrono::high_resolution_clock; 16 | using time_point = std::chrono::time_point; 17 | using milliseconds = std::chrono::milliseconds; 18 | 19 | private: 20 | const std::weak_ptr m_timer_queue; 21 | const std::shared_ptr m_executor; 22 | const size_t m_due_time; 23 | std::atomic_size_t m_frequency; 24 | time_point m_deadline; // set by the c.tor, changed only by the timer_queue thread. 25 | std::atomic_bool m_cancelled; 26 | const bool m_is_oneshot; 27 | 28 | static time_point make_deadline(milliseconds diff) noexcept { 29 | return clock_type::now() + diff; 30 | } 31 | 32 | public: 33 | timer_state_base(size_t due_time, 34 | size_t frequency, 35 | std::shared_ptr executor, 36 | std::weak_ptr timer_queue, 37 | bool is_oneshot) noexcept; 38 | 39 | virtual ~timer_state_base() noexcept = default; 40 | 41 | virtual void execute() = 0; 42 | 43 | void fire(); 44 | 45 | bool expired(const time_point now) const noexcept { 46 | return m_deadline <= now; 47 | } 48 | 49 | time_point get_deadline() const noexcept { 50 | return m_deadline; 51 | } 52 | 53 | size_t get_frequency() const noexcept { 54 | return m_frequency.load(std::memory_order_relaxed); 55 | } 56 | 57 | size_t get_due_time() const noexcept { 58 | return m_due_time; // no need to synchronize, const anyway. 59 | } 60 | 61 | bool is_oneshot() const noexcept { 62 | return m_is_oneshot; 63 | } 64 | 65 | std::shared_ptr get_executor() const noexcept { 66 | return m_executor; 67 | } 68 | 69 | std::weak_ptr get_timer_queue() const noexcept { 70 | return m_timer_queue; 71 | } 72 | 73 | void set_new_frequency(size_t new_frequency) noexcept { 74 | m_frequency.store(new_frequency, std::memory_order_relaxed); 75 | } 76 | 77 | void cancel() noexcept { 78 | m_cancelled.store(true, std::memory_order_relaxed); 79 | } 80 | 81 | bool cancelled() const noexcept { 82 | return m_cancelled.load(std::memory_order_relaxed); 83 | } 84 | }; 85 | 86 | template 87 | class timer_state final : public timer_state_base { 88 | 89 | private: 90 | callable_type m_callable; 91 | 92 | public: 93 | template 94 | timer_state(size_t due_time, 95 | size_t frequency, 96 | std::shared_ptr executor, 97 | std::weak_ptr timer_queue, 98 | bool is_oneshot, 99 | given_callable_type&& callable) : 100 | timer_state_base(due_time, frequency, std::move(executor), std::move(timer_queue), is_oneshot), 101 | m_callable(std::forward(callable)) {} 102 | 103 | void execute() override { 104 | if (cancelled()) { 105 | return; 106 | } 107 | 108 | m_callable(); 109 | } 110 | }; 111 | } // namespace concurrencpp::details 112 | 113 | namespace concurrencpp { 114 | class CRCPP_API timer { 115 | 116 | private: 117 | std::shared_ptr m_state; 118 | 119 | void throw_if_empty(const char* error_message) const; 120 | 121 | public: 122 | timer() noexcept = default; 123 | ~timer() noexcept; 124 | 125 | timer(std::shared_ptr timer_impl) noexcept; 126 | 127 | timer(timer&& rhs) noexcept = default; 128 | timer& operator=(timer&& rhs) noexcept; 129 | 130 | timer(const timer&) = delete; 131 | timer& operator=(const timer&) = delete; 132 | 133 | void cancel(); 134 | 135 | std::chrono::milliseconds get_due_time() const; 136 | std::shared_ptr get_executor() const; 137 | std::weak_ptr get_timer_queue() const; 138 | 139 | std::chrono::milliseconds get_frequency() const; 140 | void set_frequency(std::chrono::milliseconds new_frequency); 141 | 142 | explicit operator bool() const noexcept { 143 | return static_cast(m_state); 144 | } 145 | }; 146 | } // namespace concurrencpp 147 | 148 | #endif // CONCURRENCPP_TIMER_H 149 | -------------------------------------------------------------------------------- /include/concurrencpp/utils/bind.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_BIND_H 2 | #define CONCURRENCPP_BIND_H 3 | 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::details { 8 | template 9 | auto&& bind(callable_type&& callable) { 10 | return std::forward(callable); // no arguments to bind 11 | } 12 | 13 | template 14 | auto bind(callable_type&& callable, argument_types&&... arguments) { 15 | constexpr static auto inti = std::is_nothrow_invocable_v; 16 | return [callable = std::forward(callable), 17 | tuple = std::make_tuple(std::forward(arguments)...)]() mutable noexcept(inti) -> decltype(auto) { 18 | return std::apply(callable, tuple); 19 | }; 20 | } 21 | 22 | template 23 | auto&& bind_with_try_catch_impl(std::true_type /*is_noexcept*/, callable_type&& callable) { 24 | return std::forward(callable); 25 | } 26 | 27 | template 28 | auto bind_with_try_catch_impl(std::false_type /*is_noexcept*/, callable_type&& callable) { 29 | return [callable = std::forward(callable)]() mutable noexcept { 30 | try { 31 | callable(); 32 | } catch (...) { 33 | // do nothing 34 | } 35 | }; // no arguments to bind 36 | } 37 | 38 | template 39 | auto bind_with_try_catch(callable_type&& callable) { 40 | using is_noexcept = typename std::is_nothrow_invocable::type; 41 | return bind_with_try_catch_impl(is_noexcept {}, std::forward(callable)); 42 | } 43 | 44 | template 45 | auto bind_with_try_catch(callable_type&& callable, argument_types&&... arguments) { 46 | return bind_with_try_catch(bind(std::forward(callable), std::forward(arguments)...)); 47 | } 48 | } // namespace concurrencpp::details 49 | 50 | #endif -------------------------------------------------------------------------------- /include/concurrencpp/utils/slist.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_SLIST_H 2 | #define CONCURRENCPP_SLIST_H 3 | 4 | #include 5 | 6 | namespace concurrencpp::details { 7 | template 8 | class slist { 9 | 10 | private: 11 | node_type* m_head = nullptr; 12 | node_type* m_tail = nullptr; 13 | 14 | void assert_state() const noexcept { 15 | if (m_head == nullptr) { 16 | assert(m_tail == nullptr); 17 | return; 18 | } 19 | 20 | assert(m_tail != nullptr); 21 | } 22 | 23 | public: 24 | slist() noexcept = default; 25 | 26 | slist(slist&& rhs) noexcept : m_head(rhs.m_head), m_tail(rhs.m_tail) { 27 | rhs.m_head = nullptr; 28 | rhs.m_tail = nullptr; 29 | } 30 | 31 | bool empty() const noexcept { 32 | assert_state(); 33 | return m_head == nullptr; 34 | } 35 | 36 | void push_back(node_type& node) noexcept { 37 | assert_state(); 38 | 39 | if (m_head == nullptr) { 40 | m_head = m_tail = &node; 41 | return; 42 | } 43 | 44 | m_tail->next = &node; 45 | m_tail = &node; 46 | } 47 | 48 | node_type* pop_front() noexcept { 49 | assert_state(); 50 | const auto node = m_head; 51 | if (node == nullptr) { 52 | return nullptr; 53 | } 54 | 55 | m_head = m_head->next; 56 | if (m_head == nullptr) { 57 | m_tail = nullptr; 58 | } 59 | 60 | return node; 61 | } 62 | }; 63 | } // namespace concurrencpp::details 64 | 65 | #endif -------------------------------------------------------------------------------- /sandbox/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(sandbox LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | FetchContent_Declare(concurrencpp SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") 7 | FetchContent_MakeAvailable(concurrencpp) 8 | 9 | include(../cmake/coroutineOptions.cmake) 10 | 11 | add_executable(sandbox main.cpp) 12 | 13 | target_compile_features(sandbox PRIVATE cxx_std_20) 14 | 15 | target_link_libraries(sandbox PRIVATE concurrencpp::concurrencpp) 16 | 17 | target_coroutine_options(sandbox) 18 | -------------------------------------------------------------------------------- /sandbox/main.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | int main() { 6 | concurrencpp::runtime runtime; 7 | auto result = runtime.thread_pool_executor()->submit([] { 8 | std::cout << "hello world" << std::endl; 9 | }); 10 | 11 | result.get(); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /source/executors/executor.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/executors/executor.h" 2 | #include "concurrencpp/executors/constants.h" 3 | 4 | #include "concurrencpp/errors.h" 5 | #include "concurrencpp/threads/thread.h" 6 | 7 | void concurrencpp::details::throw_runtime_shutdown_exception(std::string_view executor_name) { 8 | const auto error_msg = std::string(executor_name) + consts::k_executor_shutdown_err_msg; 9 | throw errors::runtime_shutdown(error_msg); 10 | } 11 | 12 | std::string concurrencpp::details::make_executor_worker_name(std::string_view executor_name) { 13 | return std::string(executor_name) + " worker"; 14 | } 15 | -------------------------------------------------------------------------------- /source/executors/thread_executor.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/executors/constants.h" 2 | #include "concurrencpp/executors/thread_executor.h" 3 | 4 | using concurrencpp::thread_executor; 5 | 6 | thread_executor::thread_executor(const std::function& thread_started_callback, 7 | const std::function& thread_terminated_callback) : 8 | derivable_executor(details::consts::k_thread_executor_name), 9 | m_abort(false), m_atomic_abort(false), m_thread_started_callback(thread_started_callback), 10 | m_thread_terminated_callback(thread_terminated_callback) {} 11 | 12 | thread_executor::~thread_executor() noexcept { 13 | assert(m_workers.empty()); 14 | assert(m_last_retired.empty()); 15 | } 16 | 17 | void thread_executor::enqueue_impl(std::unique_lock& lock, concurrencpp::task& task) { 18 | assert(lock.owns_lock()); 19 | 20 | auto& new_thread = m_workers.emplace_front(); 21 | new_thread = details::thread( 22 | details::make_executor_worker_name(name), 23 | [this, self_it = m_workers.begin(), task = std::move(task)]() mutable { 24 | task(); 25 | retire_worker(self_it); 26 | }, 27 | m_thread_started_callback, 28 | m_thread_terminated_callback); 29 | } 30 | 31 | void thread_executor::enqueue(concurrencpp::task task) { 32 | std::unique_lock lock(m_lock); 33 | if (m_abort) { 34 | details::throw_runtime_shutdown_exception(name); 35 | } 36 | 37 | enqueue_impl(lock, task); 38 | } 39 | 40 | void thread_executor::enqueue(std::span tasks) { 41 | std::unique_lock lock(m_lock); 42 | if (m_abort) { 43 | details::throw_runtime_shutdown_exception(name); 44 | } 45 | 46 | for (auto& task : tasks) { 47 | enqueue_impl(lock, task); 48 | } 49 | } 50 | 51 | int thread_executor::max_concurrency_level() const noexcept { 52 | return details::consts::k_thread_executor_max_concurrency_level; 53 | } 54 | 55 | bool thread_executor::shutdown_requested() const { 56 | return m_atomic_abort.load(std::memory_order_relaxed); 57 | } 58 | 59 | void thread_executor::shutdown() { 60 | const auto abort = m_atomic_abort.exchange(true, std::memory_order_relaxed); 61 | if (abort) { 62 | return; // shutdown had been called before. 63 | } 64 | 65 | std::unique_lock lock(m_lock); 66 | m_abort = true; 67 | m_condition.wait(lock, [this] { 68 | return m_workers.empty(); 69 | }); 70 | 71 | if (m_last_retired.empty()) { 72 | return; 73 | } 74 | 75 | assert(m_last_retired.size() == 1); 76 | m_last_retired.front().join(); 77 | m_last_retired.clear(); 78 | } 79 | 80 | void thread_executor::retire_worker(std::list::iterator it) { 81 | std::unique_lock lock(m_lock); 82 | auto last_retired = std::move(m_last_retired); 83 | m_last_retired.splice(m_last_retired.begin(), m_workers, it); 84 | 85 | lock.unlock(); 86 | m_condition.notify_one(); 87 | 88 | if (last_retired.empty()) { 89 | return; 90 | } 91 | 92 | assert(last_retired.size() == 1); 93 | last_retired.front().join(); 94 | } -------------------------------------------------------------------------------- /source/results/impl/result_state.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/results/impl/result_state.h" 2 | #include "concurrencpp/results/impl/shared_result_state.h" 3 | 4 | using concurrencpp::details::result_state_base; 5 | 6 | void result_state_base::assert_done() const noexcept { 7 | assert(m_pc_state.load(std::memory_order_acquire) == pc_state::producer_done); 8 | } 9 | 10 | void result_state_base::wait() { 11 | const auto state = m_pc_state.load(std::memory_order_acquire); 12 | if (state == pc_state::producer_done) { 13 | return; 14 | } 15 | 16 | auto expected_state = pc_state::idle; 17 | const auto idle = m_pc_state.compare_exchange_strong(expected_state, 18 | pc_state::consumer_waiting, 19 | std::memory_order_acq_rel, 20 | std::memory_order_acquire); 21 | 22 | if (!idle) { 23 | assert_done(); 24 | return; 25 | } 26 | 27 | while (true) { 28 | if (m_pc_state.load(std::memory_order_acquire) == pc_state::producer_done) { 29 | break; 30 | } 31 | 32 | m_pc_state.wait(pc_state::consumer_waiting, std::memory_order_acquire); 33 | } 34 | 35 | assert_done(); 36 | } 37 | 38 | bool result_state_base::await(coroutine_handle caller_handle) noexcept { 39 | const auto state = m_pc_state.load(std::memory_order_acquire); 40 | if (state == pc_state::producer_done) { 41 | return false; // don't suspend 42 | } 43 | 44 | m_consumer.set_await_handle(caller_handle); 45 | 46 | auto expected_state = pc_state::idle; 47 | const auto idle = m_pc_state.compare_exchange_strong(expected_state, 48 | pc_state::consumer_set, 49 | std::memory_order_acq_rel, 50 | std::memory_order_acquire); 51 | 52 | if (!idle) { 53 | assert_done(); 54 | } 55 | 56 | return idle; // if idle = true, suspend 57 | } 58 | 59 | result_state_base::pc_state result_state_base::when_any(const std::shared_ptr& when_any_state) noexcept { 60 | const auto state = m_pc_state.load(std::memory_order_acquire); 61 | if (state == pc_state::producer_done) { 62 | return state; 63 | } 64 | 65 | m_consumer.set_when_any_context(when_any_state); 66 | 67 | auto expected_state = pc_state::idle; 68 | const auto idle = m_pc_state.compare_exchange_strong(expected_state, 69 | pc_state::consumer_set, 70 | std::memory_order_acq_rel, 71 | std::memory_order_acquire); 72 | 73 | if (!idle) { 74 | assert_done(); 75 | } 76 | 77 | return state; 78 | } 79 | 80 | void concurrencpp::details::result_state_base::share(const std::shared_ptr& shared_result_state) noexcept { 81 | const auto state = m_pc_state.load(std::memory_order_acquire); 82 | if (state == pc_state::producer_done) { 83 | return shared_result_state->on_result_finished(); 84 | } 85 | 86 | m_consumer.set_shared_context(shared_result_state); 87 | 88 | auto expected_state = pc_state::idle; 89 | const auto idle = m_pc_state.compare_exchange_strong(expected_state, 90 | pc_state::consumer_set, 91 | std::memory_order_acq_rel, 92 | std::memory_order_acquire); 93 | 94 | if (idle) { 95 | return; 96 | } 97 | 98 | assert_done(); 99 | shared_result_state->on_result_finished(); 100 | } 101 | 102 | void result_state_base::try_rewind_consumer() noexcept { 103 | const auto pc_state = m_pc_state.load(std::memory_order_acquire); 104 | if (pc_state != pc_state::consumer_set) { 105 | return; 106 | } 107 | 108 | auto expected_consumer_state = pc_state::consumer_set; 109 | const auto consumer = m_pc_state.compare_exchange_strong(expected_consumer_state, 110 | pc_state::idle, 111 | std::memory_order_acq_rel, 112 | std::memory_order_acquire); 113 | 114 | if (!consumer) { 115 | assert_done(); 116 | return; 117 | } 118 | 119 | m_consumer.clear(); 120 | } 121 | -------------------------------------------------------------------------------- /source/results/impl/shared_result_state.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/results/impl/shared_result_state.h" 2 | 3 | using concurrencpp::details::shared_result_state_base; 4 | 5 | concurrencpp::details::shared_await_context* shared_result_state_base::result_ready_constant() noexcept { 6 | return reinterpret_cast(-1); 7 | } 8 | 9 | concurrencpp::result_status concurrencpp::details::shared_result_state_base::status() const noexcept { 10 | return m_status.load(std::memory_order_acquire); 11 | } 12 | 13 | bool shared_result_state_base::await(shared_await_context& awaiter) noexcept { 14 | while (true) { 15 | auto awaiter_before = m_awaiters.load(std::memory_order_acquire); 16 | if (awaiter_before == result_ready_constant()) { 17 | return false; 18 | } 19 | 20 | awaiter.next = awaiter_before; 21 | const auto swapped = m_awaiters.compare_exchange_weak(awaiter_before, &awaiter, std::memory_order_acq_rel); 22 | if (swapped) { 23 | return true; 24 | } 25 | } 26 | } 27 | 28 | void concurrencpp::details::shared_result_state_base::wait() noexcept { 29 | if (status() == result_status::idle) { 30 | m_status.wait(result_status::idle, std::memory_order_acquire); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /source/task.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/task.h" 2 | #include "concurrencpp/results/impl/consumer_context.h" 3 | 4 | #include 5 | 6 | using concurrencpp::task; 7 | using concurrencpp::details::vtable; 8 | 9 | static_assert(sizeof(task) == concurrencpp::details::task_constants::total_size, 10 | "concurrencpp::task - object size is bigger than a cache-line."); 11 | 12 | using concurrencpp::details::callable_vtable; 13 | using concurrencpp::details::await_via_functor; 14 | 15 | namespace concurrencpp::details { 16 | namespace { 17 | class coroutine_handle_functor { 18 | 19 | private: 20 | coroutine_handle m_coro_handle; 21 | 22 | public: 23 | coroutine_handle_functor() noexcept : m_coro_handle() {} 24 | 25 | coroutine_handle_functor(const coroutine_handle_functor&) = delete; 26 | coroutine_handle_functor& operator=(const coroutine_handle_functor&) = delete; 27 | 28 | coroutine_handle_functor(coroutine_handle coro_handle) noexcept : m_coro_handle(coro_handle) {} 29 | 30 | coroutine_handle_functor(coroutine_handle_functor&& rhs) noexcept : m_coro_handle(std::exchange(rhs.m_coro_handle, {})) {} 31 | 32 | ~coroutine_handle_functor() noexcept { 33 | if (static_cast(m_coro_handle)) { 34 | m_coro_handle.destroy(); 35 | } 36 | } 37 | 38 | void execute_destroy() noexcept { 39 | auto coro_handle = std::exchange(m_coro_handle, {}); 40 | coro_handle(); 41 | } 42 | 43 | void operator()() noexcept { 44 | execute_destroy(); 45 | } 46 | }; 47 | } // namespace 48 | 49 | } // namespace concurrencpp::details 50 | 51 | using concurrencpp::details::coroutine_handle_functor; 52 | 53 | void task::build(task&& rhs) noexcept { 54 | m_vtable = std::exchange(rhs.m_vtable, nullptr); 55 | if (m_vtable == nullptr) { 56 | return; 57 | } 58 | 59 | if (contains(m_vtable)) { 60 | return callable_vtable::move_destroy(rhs.m_buffer, m_buffer); 61 | } 62 | 63 | if (contains(m_vtable)) { 64 | return callable_vtable::move_destroy(rhs.m_buffer, m_buffer); 65 | } 66 | 67 | const auto move_destroy_fn = m_vtable->move_destroy_fn; 68 | if (vtable::trivially_copiable_destructible(move_destroy_fn)) { 69 | std::memcpy(m_buffer, rhs.m_buffer, details::task_constants::buffer_size); 70 | return; 71 | } 72 | 73 | move_destroy_fn(rhs.m_buffer, m_buffer); 74 | } 75 | 76 | void task::build(details::coroutine_handle coro_handle) noexcept { 77 | build(details::coroutine_handle_functor {coro_handle}); 78 | } 79 | 80 | bool task::contains_coroutine_handle() const noexcept { 81 | return contains(); 82 | } 83 | 84 | task::task() noexcept : m_buffer(), m_vtable(nullptr) {} 85 | 86 | task::task(task&& rhs) noexcept { 87 | build(std::move(rhs)); 88 | } 89 | 90 | task::task(details::coroutine_handle coro_handle) noexcept { 91 | build(coro_handle); 92 | } 93 | 94 | task::~task() noexcept { 95 | clear(); 96 | } 97 | 98 | void task::operator()() { 99 | const auto vtable = std::exchange(m_vtable, nullptr); 100 | if (vtable == nullptr) { 101 | return; 102 | } 103 | 104 | if (contains(vtable)) { 105 | return callable_vtable::execute_destroy(m_buffer); 106 | } 107 | 108 | if (contains(vtable)) { 109 | return callable_vtable::execute_destroy(m_buffer); 110 | } 111 | 112 | vtable->execute_destroy_fn(m_buffer); 113 | } 114 | 115 | task& task::operator=(task&& rhs) noexcept { 116 | if (this == &rhs) { 117 | return *this; 118 | } 119 | 120 | clear(); 121 | build(std::move(rhs)); 122 | return *this; 123 | } 124 | 125 | void task::clear() noexcept { 126 | if (m_vtable == nullptr) { 127 | return; 128 | } 129 | 130 | const auto vtable = std::exchange(m_vtable, nullptr); 131 | 132 | if (contains(vtable)) { 133 | return callable_vtable::destroy(m_buffer); 134 | } 135 | 136 | if (contains(vtable)) { 137 | return callable_vtable::destroy(m_buffer); 138 | } 139 | 140 | auto destroy_fn = vtable->destroy_fn; 141 | if (vtable::trivially_destructible(destroy_fn)) { 142 | return; 143 | } 144 | 145 | destroy_fn(m_buffer); 146 | } 147 | 148 | task::operator bool() const noexcept { 149 | return m_vtable != nullptr; 150 | } 151 | -------------------------------------------------------------------------------- /source/threads/async_condition_variable.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/results/resume_on.h" 2 | #include "concurrencpp/threads/constants.h" 3 | #include "concurrencpp/threads/async_condition_variable.h" 4 | 5 | using concurrencpp::executor; 6 | using concurrencpp::lazy_result; 7 | using concurrencpp::scoped_async_lock; 8 | using concurrencpp::async_condition_variable; 9 | 10 | using concurrencpp::details::cv_awaiter; 11 | 12 | /* 13 | cv_awaiter 14 | */ 15 | 16 | cv_awaiter::cv_awaiter(async_condition_variable& parent, scoped_async_lock& lock) noexcept : m_parent(parent), m_lock(lock) {} 17 | 18 | void cv_awaiter::await_suspend(details::coroutine_handle caller_handle) { 19 | m_caller_handle = caller_handle; 20 | 21 | std::unique_lock lock(m_parent.m_lock); 22 | m_lock.unlock(); 23 | 24 | m_parent.m_awaiters.push_back(*this); 25 | } 26 | 27 | void cv_awaiter::resume() noexcept { 28 | assert(static_cast(m_caller_handle)); 29 | assert(!m_caller_handle.done()); 30 | m_caller_handle(); 31 | } 32 | 33 | /* 34 | async_condition_variable 35 | */ 36 | 37 | async_condition_variable::~async_condition_variable() noexcept { 38 | #ifdef CRCPP_DEBUG_MODE 39 | std::unique_lock lock(m_lock); 40 | assert(m_awaiters.empty() && "concurrencpp::async_condition_variable is deleted while being used."); 41 | #endif 42 | } 43 | 44 | void async_condition_variable::verify_await_params(const std::shared_ptr& resume_executor, const scoped_async_lock& lock) { 45 | if (!static_cast(resume_executor)) { 46 | throw std::invalid_argument(details::consts::k_async_condition_variable_await_invalid_resume_executor_err_msg); 47 | } 48 | 49 | if (!lock.owns_lock()) { 50 | throw std::invalid_argument(details::consts::k_async_condition_variable_await_lock_unlocked_err_msg); 51 | } 52 | } 53 | 54 | lazy_result async_condition_variable::await_impl(std::shared_ptr resume_executor, scoped_async_lock& lock) { 55 | co_await details::cv_awaiter(*this, lock); 56 | assert(!lock.owns_lock()); 57 | co_await resume_on(resume_executor); // TODO: optimize this when get_current_executor is available 58 | co_await lock.lock(resume_executor); 59 | } 60 | 61 | lazy_result async_condition_variable::await(std::shared_ptr resume_executor, scoped_async_lock& lock) { 62 | verify_await_params(resume_executor, lock); 63 | return await_impl(std::move(resume_executor), lock); 64 | } 65 | 66 | void async_condition_variable::notify_one() { 67 | std::unique_lock lock(m_lock); 68 | const auto awaiter = m_awaiters.pop_front(); 69 | lock.unlock(); 70 | 71 | if (awaiter != nullptr) { 72 | awaiter->resume(); 73 | } 74 | } 75 | 76 | void async_condition_variable::notify_all() { 77 | std::unique_lock lock(m_lock); 78 | auto awaiters = std::move(m_awaiters); 79 | lock.unlock(); 80 | 81 | while (true) { 82 | const auto awaiter = awaiters.pop_front(); 83 | if (awaiter == nullptr) { 84 | return; // no more awaiters 85 | } 86 | 87 | awaiter->resume(); 88 | } 89 | } -------------------------------------------------------------------------------- /source/threads/thread.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/threads/thread.h" 2 | 3 | #include "concurrencpp/platform_defs.h" 4 | 5 | #include 6 | 7 | #include "concurrencpp/runtime/constants.h" 8 | 9 | using concurrencpp::details::thread; 10 | 11 | namespace concurrencpp::details { 12 | namespace { 13 | std::uintptr_t generate_thread_id() noexcept { 14 | static std::atomic_uintptr_t s_id_seed = 1; 15 | return s_id_seed.fetch_add(1, std::memory_order_relaxed); 16 | } 17 | 18 | struct thread_per_thread_data { 19 | const std::uintptr_t id = generate_thread_id(); 20 | }; 21 | 22 | thread_local thread_per_thread_data s_tl_thread_per_data; 23 | } // namespace 24 | } // namespace concurrencpp::details 25 | 26 | std::thread::id thread::get_id() const noexcept { 27 | return m_thread.get_id(); 28 | } 29 | 30 | std::uintptr_t thread::get_current_virtual_id() noexcept { 31 | return s_tl_thread_per_data.id; 32 | } 33 | 34 | bool thread::joinable() const noexcept { 35 | return m_thread.joinable(); 36 | } 37 | 38 | void thread::join() { 39 | m_thread.join(); 40 | } 41 | 42 | size_t thread::hardware_concurrency() noexcept { 43 | const auto hc = std::thread::hardware_concurrency(); 44 | return (hc != 0) ? hc : consts::k_default_number_of_cores; 45 | } 46 | 47 | #ifdef CRCPP_WIN_OS 48 | 49 | # include 50 | 51 | void thread::set_name(std::string_view name) noexcept { 52 | const std::wstring utf16_name(name.begin(), 53 | name.end()); // concurrencpp strings are always ASCII (english only) 54 | ::SetThreadDescription(::GetCurrentThread(), utf16_name.data()); 55 | } 56 | 57 | #elif defined(CRCPP_MINGW_OS) 58 | 59 | # include 60 | 61 | void thread::set_name(std::string_view name) noexcept { 62 | ::pthread_setname_np(::pthread_self(), name.data()); 63 | } 64 | 65 | #elif defined(CRCPP_UNIX_OS) 66 | 67 | # include 68 | 69 | void thread::set_name(std::string_view name) noexcept { 70 | ::pthread_setname_np(::pthread_self(), name.data()); 71 | } 72 | 73 | #elif defined(CRCPP_MAC_OS) 74 | 75 | # include 76 | 77 | void thread::set_name(std::string_view name) noexcept { 78 | ::pthread_setname_np(name.data()); 79 | } 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /source/timers/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/timers/timer.h" 2 | #include "concurrencpp/timers/timer_queue.h" 3 | #include "concurrencpp/timers/constants.h" 4 | 5 | #include "concurrencpp/errors.h" 6 | #include "concurrencpp/results/result.h" 7 | #include "concurrencpp/executors/executor.h" 8 | 9 | using concurrencpp::timer; 10 | using concurrencpp::details::timer_state; 11 | using concurrencpp::details::timer_state_base; 12 | 13 | timer_state_base::timer_state_base(size_t due_time, 14 | size_t frequency, 15 | std::shared_ptr executor, 16 | std::weak_ptr timer_queue, 17 | bool is_oneshot) noexcept : 18 | m_timer_queue(std::move(timer_queue)), 19 | m_executor(std::move(executor)), m_due_time(due_time), m_frequency(frequency), m_deadline(make_deadline(milliseconds(due_time))), 20 | m_cancelled(false), m_is_oneshot(is_oneshot) { 21 | assert(static_cast(m_executor)); 22 | } 23 | 24 | void timer_state_base::fire() { 25 | const auto frequency = m_frequency.load(std::memory_order_relaxed); 26 | m_deadline = make_deadline(milliseconds(frequency)); 27 | 28 | assert(static_cast(m_executor)); 29 | 30 | m_executor->post([self = shared_from_this()]() mutable { 31 | self->execute(); 32 | }); 33 | } 34 | 35 | timer::timer(std::shared_ptr timer_impl) noexcept : m_state(std::move(timer_impl)) {} 36 | 37 | timer::~timer() noexcept { 38 | cancel(); 39 | } 40 | 41 | void timer::throw_if_empty(const char* error_message) const { 42 | if (static_cast(m_state)) { 43 | return; 44 | } 45 | 46 | throw errors::empty_timer(error_message); 47 | } 48 | 49 | std::chrono::milliseconds timer::get_due_time() const { 50 | throw_if_empty(details::consts::k_timer_empty_get_due_time_err_msg); 51 | return std::chrono::milliseconds(m_state->get_due_time()); 52 | } 53 | 54 | std::chrono::milliseconds timer::get_frequency() const { 55 | throw_if_empty(details::consts::k_timer_empty_get_frequency_err_msg); 56 | return std::chrono::milliseconds(m_state->get_frequency()); 57 | } 58 | 59 | std::shared_ptr timer::get_executor() const { 60 | throw_if_empty(details::consts::k_timer_empty_get_executor_err_msg); 61 | return m_state->get_executor(); 62 | } 63 | 64 | std::weak_ptr timer::get_timer_queue() const { 65 | throw_if_empty(details::consts::k_timer_empty_get_timer_queue_err_msg); 66 | return m_state->get_timer_queue(); 67 | } 68 | 69 | void timer::cancel() { 70 | if (!static_cast(m_state)) { 71 | return; 72 | } 73 | 74 | auto state = std::move(m_state); 75 | state->cancel(); 76 | 77 | auto timer_queue = state->get_timer_queue().lock(); 78 | 79 | if (!static_cast(timer_queue)) { 80 | return; 81 | } 82 | 83 | timer_queue->remove_internal_timer(std::move(state)); 84 | } 85 | 86 | void timer::set_frequency(std::chrono::milliseconds new_frequency) { 87 | throw_if_empty(details::consts::k_timer_empty_set_frequency_err_msg); 88 | return m_state->set_new_frequency(new_frequency.count()); 89 | } 90 | 91 | timer& timer::operator=(timer&& rhs) noexcept { 92 | if (this == &rhs) { 93 | return *this; 94 | } 95 | 96 | if (static_cast(*this)) { 97 | cancel(); 98 | } 99 | 100 | m_state = std::move(rhs.m_state); 101 | return *this; 102 | } 103 | -------------------------------------------------------------------------------- /test/include/infra/assertions.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_ASSERTIONS_H 2 | #define CONCURRENCPP_ASSERTIONS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace concurrencpp::tests::details { 9 | std::string to_string(bool value); 10 | std::string to_string(int value); 11 | std::string to_string(long value); 12 | std::string to_string(long long value); 13 | std::string to_string(unsigned value); 14 | std::string to_string(unsigned long value); 15 | std::string to_string(unsigned long long value); 16 | std::string to_string(float value); 17 | std::string to_string(double value); 18 | std::string to_string(long double value); 19 | const std::string& to_string(const std::string& str); 20 | std::string to_string(const char* str); 21 | std::string to_string(const std::string_view str); 22 | 23 | template 24 | std::string to_string(type* value) { 25 | return std::string("pointer[") + to_string(reinterpret_cast(value)) + "]"; 26 | } 27 | 28 | template 29 | std::string to_string(const type&) { 30 | return "{object}"; 31 | } 32 | 33 | void assert_equal_failed_impl(const std::string& a, const std::string& b); 34 | void assert_not_equal_failed_impl(const std::string& a, const std::string& b); 35 | void assert_bigger_failed_impl(const std::string& a, const std::string& b); 36 | void assert_smaller_failed_impl(const std::string& a, const std::string& b); 37 | void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b); 38 | void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b); 39 | } // namespace concurrencpp::tests::details 40 | 41 | namespace concurrencpp::tests { 42 | void assert_true(bool condition); 43 | void assert_false(bool condition); 44 | 45 | template 46 | void assert_equal(const a_type& given_value, const b_type& expected_value) { 47 | if (given_value == expected_value) { 48 | return; 49 | } 50 | 51 | details::assert_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 52 | } 53 | 54 | template 55 | inline void assert_not_equal(const a_type& given_value, const b_type& expected_value) { 56 | if (given_value != expected_value) { 57 | return; 58 | } 59 | 60 | details::assert_not_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 61 | } 62 | 63 | template 64 | void assert_bigger(const a_type& given_value, const b_type& expected_value) { 65 | if (given_value > expected_value) { 66 | return; 67 | } 68 | 69 | details::assert_bigger_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 70 | } 71 | 72 | template 73 | void assert_smaller(const a_type& given_value, const b_type& expected_value) { 74 | if (given_value < expected_value) { 75 | return; 76 | } 77 | 78 | details::assert_smaller_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 79 | } 80 | 81 | template 82 | void assert_bigger_equal(const a_type& given_value, const b_type& expected_value) { 83 | if (given_value >= expected_value) { 84 | return; 85 | } 86 | 87 | details::assert_bigger_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 88 | } 89 | 90 | template 91 | void assert_smaller_equal(const a_type& given_value, const b_type& expected_value) { 92 | if (given_value <= expected_value) { 93 | return; 94 | } 95 | 96 | details::assert_smaller_equal_failed_impl(details::to_string(given_value), details::to_string(expected_value)); 97 | } 98 | 99 | template 100 | void assert_throws(task_type&& task) { 101 | try { 102 | task(); 103 | } catch (const exception_type&) { 104 | return; 105 | } catch (...) { 106 | } 107 | 108 | assert_false(true); 109 | } 110 | 111 | template 112 | void assert_throws_with_error_message(task_type&& task, std::string_view error_msg) { 113 | try { 114 | task(); 115 | } catch (const exception_type& e) { 116 | assert_equal(error_msg, e.what()); 117 | return; 118 | } catch (...) { 119 | } 120 | 121 | assert_false(true); 122 | } 123 | 124 | template 125 | void assert_throws_contains_error_message(task_type&& task, std::string_view error_msg) { 126 | try { 127 | task(); 128 | } catch (const exception_type& e) { 129 | const auto pos = std::string(e.what()).find(error_msg); 130 | assert_not_equal(pos, std::string::npos); 131 | return; 132 | } catch (...) { 133 | } 134 | 135 | assert_false(true); 136 | } 137 | 138 | } // namespace concurrencpp::tests 139 | 140 | #endif // CONCURRENCPP_ASSERTIONS_H 141 | -------------------------------------------------------------------------------- /test/include/infra/tester.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_TESTER_H 2 | #define CONCURRENCPP_TESTER_H 3 | 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::tests { 8 | class test_step { 9 | 10 | private: 11 | const char* m_step_name; 12 | std::function m_step; 13 | 14 | public: 15 | test_step(const char* step_name, std::function callable); 16 | 17 | void launch_test_step() noexcept; 18 | }; 19 | 20 | class tester { 21 | 22 | private: 23 | const char* m_test_name; 24 | std::deque m_steps; 25 | 26 | public: 27 | tester(const char* test_name) noexcept; 28 | 29 | void launch_test() noexcept; 30 | void add_step(const char* step_name, std::function callable); 31 | }; 32 | 33 | } // namespace concurrencpp::tests 34 | 35 | #endif // CONCURRENCPP_TESTER_H 36 | -------------------------------------------------------------------------------- /test/include/utils/custom_exception.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_CUSTOM_EXCEPTION_H 2 | #define CONCURRENCPP_CUSTOM_EXCEPTION_H 3 | 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::tests { 8 | struct custom_exception : public std::exception { 9 | const intptr_t id; 10 | 11 | custom_exception(intptr_t id) noexcept : id(id) {} 12 | }; 13 | } // namespace concurrencpp::tests 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /test/include/utils/executor_shutdowner.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_EXECUTOR_TEST_HELPERS_H 2 | #define CONCURRENCPP_EXECUTOR_TEST_HELPERS_H 3 | 4 | #include "concurrencpp/executors/executor.h" 5 | 6 | namespace concurrencpp::tests { 7 | struct executor_shutdowner { 8 | std::shared_ptr executor; 9 | 10 | executor_shutdowner(std::shared_ptr executor) noexcept : executor(std::move(executor)) {} 11 | 12 | ~executor_shutdowner() noexcept { 13 | executor->shutdown(); 14 | } 15 | }; 16 | } // namespace concurrencpp::tests 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /test/include/utils/object_observer.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_OBJECT_OBSERVER_H 2 | #define CONCURRENCPP_OBJECT_OBSERVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace concurrencpp::tests { 9 | 10 | namespace details { 11 | class object_observer_state; 12 | } 13 | 14 | class testing_stub { 15 | 16 | protected: 17 | std::shared_ptr m_state; 18 | 19 | public: 20 | testing_stub() noexcept {} 21 | 22 | testing_stub(std::shared_ptr state) noexcept : m_state(std::move(state)) {} 23 | 24 | testing_stub(testing_stub&& rhs) noexcept = default; 25 | 26 | ~testing_stub() noexcept; 27 | 28 | testing_stub& operator=(testing_stub&& rhs) noexcept; 29 | 30 | void operator()() noexcept; 31 | }; 32 | 33 | class value_testing_stub : public testing_stub { 34 | 35 | private: 36 | const size_t m_expected_return_value; 37 | 38 | public: 39 | value_testing_stub(size_t expected_return_value) noexcept : m_expected_return_value(expected_return_value) {} 40 | 41 | value_testing_stub(std::shared_ptr state, int return_value) noexcept : 42 | testing_stub(std::move(state)), m_expected_return_value(return_value) {} 43 | 44 | value_testing_stub(std::shared_ptr state, size_t return_value) noexcept : 45 | testing_stub(std::move(state)), m_expected_return_value(return_value) {} 46 | 47 | value_testing_stub(value_testing_stub&& rhs) noexcept = default; 48 | 49 | value_testing_stub& operator=(value_testing_stub&& rhs) noexcept; 50 | 51 | size_t operator()() noexcept; 52 | }; 53 | 54 | class object_observer { 55 | 56 | private: 57 | const std::shared_ptr m_state; 58 | 59 | public: 60 | object_observer(); 61 | object_observer(object_observer&&) noexcept = default; 62 | 63 | testing_stub get_testing_stub() noexcept; 64 | value_testing_stub get_testing_stub(int value) noexcept; 65 | value_testing_stub get_testing_stub(size_t value) noexcept; 66 | 67 | bool wait_execution_count(size_t count, std::chrono::milliseconds timeout); 68 | bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout); 69 | 70 | size_t get_destruction_count() const noexcept; 71 | size_t get_execution_count() const noexcept; 72 | 73 | std::unordered_map get_execution_map() const noexcept; 74 | }; 75 | 76 | } // namespace concurrencpp::tests 77 | 78 | #endif // !CONCURRENCPP_BEHAVIOURAL_TESTERS_H 79 | -------------------------------------------------------------------------------- /test/include/utils/random.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RANDOM_H 2 | #define CONCURRENCPP_RANDOM_H 3 | 4 | #include 5 | 6 | namespace concurrencpp::tests { 7 | class random { 8 | 9 | private: 10 | std::random_device rd; 11 | std::mt19937 mt; 12 | std::uniform_int_distribution dist; 13 | 14 | public: 15 | random() noexcept : mt(rd()), dist(-1'000'000, 1'000'000) {} 16 | 17 | intptr_t operator()() { 18 | return dist(mt); 19 | } 20 | 21 | int64_t operator()(int64_t min, int64_t max) { 22 | const auto r = (*this)(); 23 | const auto upper_limit = max - min + 1; 24 | return std::abs(r) % upper_limit + min; 25 | } 26 | }; 27 | } // namespace concurrencpp::tests 28 | 29 | #endif // CONCURRENCPP_RANDOM_H 30 | -------------------------------------------------------------------------------- /test/include/utils/test_ready_lazy_result.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_TEST_READY_LAZY_RESULT_H 2 | #define CONCURRENCPP_TEST_READY_LAZY_RESULT_H 3 | 4 | #include "concurrencpp/concurrencpp.h" 5 | 6 | #include "infra/assertions.h" 7 | #include "utils/test_generators.h" 8 | #include "utils/custom_exception.h" 9 | 10 | #include 11 | 12 | namespace concurrencpp::tests { 13 | template 14 | null_result test_ready_lazy_result(::concurrencpp::lazy_result result, const type& o) { 15 | assert_true(static_cast(result)); 16 | assert_equal(result.status(), concurrencpp::result_status::value); 17 | 18 | try { 19 | assert_equal(co_await result, o); 20 | } catch (...) { 21 | assert_true(false); 22 | } 23 | } 24 | 25 | template 26 | null_result test_ready_lazy_result(::concurrencpp::lazy_result result, 27 | std::reference_wrapper> ref) { 28 | assert_true(static_cast(result)); 29 | assert_equal(result.status(), concurrencpp::result_status::value); 30 | 31 | try { 32 | auto& result_ref = co_await result; 33 | assert_equal(&result_ref, &ref.get()); 34 | } catch (...) { 35 | assert_true(false); 36 | } 37 | } 38 | 39 | template 40 | null_result test_ready_lazy_result(::concurrencpp::lazy_result result) { 41 | return test_ready_lazy_result(std::move(result), value_gen::default_value()); 42 | } 43 | 44 | template<> 45 | inline null_result test_ready_lazy_result(::concurrencpp::lazy_result result) { 46 | assert_true(static_cast(result)); 47 | assert_equal(result.status(), concurrencpp::result_status::value); 48 | 49 | try { 50 | co_await result; // just make sure no exception is thrown. 51 | } catch (...) { 52 | assert_true(false); 53 | } 54 | } 55 | 56 | template 57 | null_result test_ready_lazy_result_custom_exception(concurrencpp::lazy_result result, const intptr_t id) { 58 | assert_true(static_cast(result)); 59 | assert_equal(result.status(), concurrencpp::result_status::exception); 60 | 61 | try { 62 | co_await result; 63 | } catch (const custom_exception& e) { 64 | assert_equal(e.id, id); 65 | co_return; 66 | } catch (...) { 67 | } 68 | 69 | assert_true(false); 70 | } 71 | } // namespace concurrencpp::tests 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /test/include/utils/test_thread_callbacks.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_TEST_THREAD_CALLBACKS_H 2 | #define CONCURRENCPP_TEST_THREAD_CALLBACKS_H 3 | 4 | #include "infra/assertions.h" 5 | #include "utils/executor_shutdowner.h" 6 | #include "concurrencpp/executors/executor.h" 7 | 8 | namespace concurrencpp::tests { 9 | template 10 | void test_thread_callbacks(executor_factory_type executor_factory, std::string_view expected_thread_name) { 11 | std::atomic_size_t thread_started_callback_invocations_num = 0; 12 | std::atomic_size_t thread_terminated_callback_invocations_num = 0; 13 | 14 | auto thread_started_callback = [&thread_started_callback_invocations_num, expected_thread_name](std::string_view thread_name) { 15 | ++thread_started_callback_invocations_num; 16 | assert_equal(thread_name, expected_thread_name); 17 | }; 18 | 19 | auto thread_terminated_callback = [&thread_terminated_callback_invocations_num, 20 | expected_thread_name](std::string_view thread_name) { 21 | ++thread_terminated_callback_invocations_num; 22 | assert_equal(thread_name, expected_thread_name); 23 | }; 24 | 25 | std::shared_ptr executor = executor_factory(thread_started_callback, thread_terminated_callback); 26 | executor_shutdowner es(executor); 27 | 28 | assert_equal(thread_started_callback_invocations_num, 0); 29 | assert_equal(thread_terminated_callback_invocations_num, 0); 30 | 31 | executor 32 | ->submit([&thread_started_callback_invocations_num, &thread_terminated_callback_invocations_num]() { 33 | assert_equal(thread_started_callback_invocations_num, 1); 34 | assert_equal(thread_terminated_callback_invocations_num, 0); 35 | }) 36 | .get(); 37 | 38 | executor->shutdown(); 39 | assert_equal(thread_started_callback_invocations_num, 1); 40 | assert_equal(thread_terminated_callback_invocations_num, 1); 41 | } 42 | } // namespace concurrencpp::tests 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /test/include/utils/throwing_executor.h: -------------------------------------------------------------------------------- 1 | #ifndef CONCURRENCPP_RESULT_TEST_EXECUTORS_H 2 | #define CONCURRENCPP_RESULT_TEST_EXECUTORS_H 3 | 4 | #include "concurrencpp/executors/executor.h" 5 | 6 | namespace concurrencpp::tests { 7 | struct executor_enqueue_exception {}; 8 | 9 | struct throwing_executor : public concurrencpp::executor { 10 | throwing_executor() : executor("throwing_executor") {} 11 | 12 | void enqueue(concurrencpp::task) override { 13 | throw executor_enqueue_exception(); 14 | } 15 | 16 | void enqueue(std::span) override { 17 | throw executor_enqueue_exception(); 18 | } 19 | 20 | int max_concurrency_level() const noexcept override { 21 | return 0; 22 | } 23 | 24 | bool shutdown_requested() const noexcept override { 25 | return false; 26 | } 27 | 28 | void shutdown() noexcept override { 29 | // do nothing 30 | } 31 | }; 32 | } // namespace concurrencpp::tests 33 | 34 | #endif // CONCURRENCPP_RESULT_HELPERS_H 35 | -------------------------------------------------------------------------------- /test/source/infra/assertions.cpp: -------------------------------------------------------------------------------- 1 | #include "infra/assertions.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::tests::details { 8 | std::string to_string(bool value) { 9 | return value ? "true" : "false"; 10 | } 11 | 12 | std::string to_string(int value) { 13 | return std::to_string(value); 14 | } 15 | 16 | std::string to_string(long value) { 17 | return std::to_string(value); 18 | } 19 | 20 | std::string to_string(long long value) { 21 | return std::to_string(value); 22 | } 23 | 24 | std::string to_string(unsigned value) { 25 | return std::to_string(value); 26 | } 27 | 28 | std::string to_string(unsigned long value) { 29 | return std::to_string(value); 30 | } 31 | 32 | std::string to_string(unsigned long long value) { 33 | return std::to_string(value); 34 | } 35 | 36 | std::string to_string(float value) { 37 | return std::to_string(value); 38 | } 39 | 40 | std::string to_string(double value) { 41 | return std::to_string(value); 42 | } 43 | 44 | std::string to_string(long double value) { 45 | return std::to_string(value); 46 | } 47 | 48 | const std::string& to_string(const std::string& str) { 49 | return str; 50 | } 51 | 52 | std::string to_string(const char* str) { 53 | return str; 54 | } 55 | 56 | std::string to_string(const std::string_view str) { 57 | return {str.begin(), str.end()}; 58 | } 59 | 60 | void assert_equal_failed_impl(const std::string& a, const std::string& b) { 61 | std::cerr << "assertion failed. expected [" << a << "] == [" << b << "]" << std::endl; 62 | std::abort(); 63 | } 64 | 65 | void assert_not_equal_failed_impl(const std::string& a, const std::string& b) { 66 | std::cerr << "assertion failed. expected [" << a << "] =/= [" << b << "]" << std::endl; 67 | std::abort(); 68 | } 69 | 70 | void assert_bigger_failed_impl(const std::string& a, const std::string& b) { 71 | std::cerr << "assertion failed. expected [" << a << "] > [" << b << "]" << std::endl; 72 | std::abort(); 73 | } 74 | 75 | void assert_smaller_failed_impl(const std::string& a, const std::string& b) { 76 | std::cerr << "assertion failed. expected [" << a << "] < [" << b << "]" << std::endl; 77 | std::abort(); 78 | } 79 | 80 | void assert_bigger_equal_failed_impl(const std::string& a, const std::string& b) { 81 | std::cerr << "assertion failed. expected [" << a << "] >= [" << b << "]" << std::endl; 82 | std::abort(); 83 | } 84 | 85 | void assert_smaller_equal_failed_impl(const std::string& a, const std::string& b) { 86 | std::cerr << "assertion failed. expected [" << a << "] <= [" << b << "]" << std::endl; 87 | std::abort(); 88 | } 89 | 90 | } // namespace concurrencpp::tests::details 91 | 92 | namespace concurrencpp::tests { 93 | void assert_true(bool condition) { 94 | if (!condition) { 95 | std::cerr << "assertion failed. expected: [true] actual: [false]."; 96 | std::abort(); 97 | } 98 | } 99 | 100 | void assert_false(bool condition) { 101 | if (condition) { 102 | std::cerr << "assertion failed. expected: [false] actual: [true]."; 103 | std::abort(); 104 | } 105 | } 106 | } // namespace concurrencpp::tests 107 | -------------------------------------------------------------------------------- /test/source/infra/tester.cpp: -------------------------------------------------------------------------------- 1 | #include "infra/tester.h" 2 | 3 | #include 4 | #include 5 | 6 | using concurrencpp::tests::test_step; 7 | using concurrencpp::tests::tester; 8 | using namespace std::chrono; 9 | 10 | test_step::test_step(const char* step_name, std::function callable) : m_step_name(step_name), m_step(std::move(callable)) {} 11 | 12 | void test_step::launch_test_step() noexcept { 13 | const auto test_start_time = system_clock::now(); 14 | std::cout << "\tTest-step started: " << m_step_name << std::endl; 15 | 16 | m_step(); 17 | 18 | const auto elapsed_time = duration_cast(system_clock::now() - test_start_time).count(); 19 | std::cout << "\tTest-step ended (" << elapsed_time << "ms)." << std::endl; 20 | } 21 | 22 | tester::tester(const char* test_name) noexcept : m_test_name(test_name) {} 23 | 24 | void tester::add_step(const char* step_name, std::function callable) { 25 | m_steps.emplace_back(step_name, std::move(callable)); 26 | } 27 | 28 | void tester::launch_test() noexcept { 29 | std::cout << "Test started: " << m_test_name << std::endl; 30 | 31 | for (auto& test_step : m_steps) { 32 | try { 33 | test_step.launch_test_step(); 34 | } catch (const std::exception& ex) { 35 | std::cerr << "\tTest step terminated with an exception : " << ex.what() << std::endl; 36 | } 37 | } 38 | 39 | std::cout << "Test ended.\n____________________" << std::endl; 40 | } 41 | -------------------------------------------------------------------------------- /test/source/tests/result_tests/make_result_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include "infra/tester.h" 4 | #include "infra/assertions.h" 5 | #include "utils/object_observer.h" 6 | #include "utils/test_ready_result.h" 7 | 8 | namespace concurrencpp::tests { 9 | template 10 | void test_make_ready_result_impl(); 11 | void test_make_ready_result(); 12 | 13 | template 14 | void test_make_exceptional_result_impl(); 15 | void test_make_exceptional_result(); 16 | } // namespace concurrencpp::tests 17 | 18 | template 19 | void concurrencpp::tests::test_make_ready_result_impl() { 20 | result result; 21 | 22 | if constexpr (std::is_same_v) { 23 | result = concurrencpp::make_ready_result(); 24 | } else { 25 | result = make_ready_result(value_gen::default_value()); 26 | } 27 | 28 | test_ready_result(std::move(result)); 29 | } 30 | 31 | void concurrencpp::tests::test_make_ready_result() { 32 | test_make_ready_result_impl(); 33 | test_make_ready_result_impl(); 34 | test_make_ready_result_impl(); 35 | test_make_ready_result_impl(); 36 | test_make_ready_result_impl(); 37 | } 38 | 39 | template 40 | void concurrencpp::tests::test_make_exceptional_result_impl() { 41 | // empty exception_ptr makes make_exceptional_result throw. 42 | assert_throws_with_error_message( 43 | [] { 44 | make_exceptional_result({}); 45 | }, 46 | concurrencpp::details::consts::k_make_exceptional_result_exception_null_error_msg); 47 | 48 | const size_t id = 123456789; 49 | auto res0 = make_exceptional_result(custom_exception(id)); 50 | test_ready_result_custom_exception(std::move(res0), id); 51 | 52 | auto res1 = make_exceptional_result(std::make_exception_ptr(custom_exception(id))); 53 | test_ready_result_custom_exception(std::move(res1), id); 54 | } 55 | 56 | void concurrencpp::tests::test_make_exceptional_result() { 57 | test_make_exceptional_result_impl(); 58 | test_make_exceptional_result_impl(); 59 | test_make_exceptional_result_impl(); 60 | test_make_exceptional_result_impl(); 61 | test_make_exceptional_result_impl(); 62 | } 63 | 64 | using namespace concurrencpp::tests; 65 | 66 | int main() { 67 | tester tester("make_result test"); 68 | 69 | tester.add_step("make_ready_result", test_make_ready_result); 70 | tester.add_step("make_exceptional_result", test_make_exceptional_result); 71 | 72 | tester.launch_test(); 73 | return 0; 74 | } -------------------------------------------------------------------------------- /test/source/tests/result_tests/resume_on_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include "infra/tester.h" 4 | #include "infra/assertions.h" 5 | #include "utils/object_observer.h" 6 | #include "utils/test_generators.h" 7 | 8 | #include 9 | 10 | namespace concurrencpp::tests { 11 | void test_resume_on_null_executor(); 12 | void test_resume_on_shutdown_executor(); 13 | void test_resume_on_shutdown_executor_delayed(); 14 | void test_resume_on_shared_ptr(); 15 | void test_resume_on_ref(); 16 | } // namespace concurrencpp::tests 17 | 18 | namespace concurrencpp::tests { 19 | result resume_on_1_executor(std::shared_ptr executor) { 20 | co_await concurrencpp::resume_on(executor); 21 | } 22 | 23 | result resume_on_many_executors_shared(std::span> executors, 24 | std::unordered_set& set) { 25 | for (auto& executor : executors) { 26 | co_await concurrencpp::resume_on(executor); 27 | set.insert(::concurrencpp::details::thread::get_current_virtual_id()); 28 | } 29 | } 30 | 31 | result resume_on_many_executors_ref(std::span executors, std::unordered_set& set) { 32 | for (auto executor : executors) { 33 | co_await concurrencpp::resume_on(*executor); 34 | set.insert(::concurrencpp::details::thread::get_current_virtual_id()); 35 | } 36 | } 37 | } // namespace concurrencpp::tests 38 | 39 | void concurrencpp::tests::test_resume_on_null_executor() { 40 | assert_throws_with_error_message( 41 | [] { 42 | resume_on_1_executor({}).get(); 43 | }, 44 | concurrencpp::details::consts::k_resume_on_null_exception_err_msg); 45 | } 46 | 47 | void concurrencpp::tests::test_resume_on_shutdown_executor() { 48 | auto ex = std::make_shared(); 49 | ex->shutdown(); 50 | 51 | assert_throws([ex] { 52 | resume_on_1_executor(ex).get(); 53 | }); 54 | } 55 | 56 | void concurrencpp::tests::test_resume_on_shutdown_executor_delayed() { 57 | auto ex = std::make_shared(); 58 | auto result = resume_on_1_executor(ex); 59 | 60 | assert_equal(ex->size(), 1); 61 | 62 | ex->shutdown(); 63 | 64 | assert_throws([&result] { 65 | result.get(); 66 | }); 67 | } 68 | 69 | void concurrencpp::tests::test_resume_on_shared_ptr() { 70 | 71 | concurrencpp::runtime runtime; 72 | std::shared_ptr executors[4]; 73 | 74 | executors[0] = runtime.thread_executor(); 75 | executors[1] = runtime.thread_pool_executor(); 76 | executors[2] = runtime.make_worker_thread_executor(); 77 | executors[3] = runtime.make_worker_thread_executor(); 78 | 79 | std::unordered_set set; 80 | resume_on_many_executors_shared(executors, set).get(); 81 | 82 | assert_equal(set.size(), std::size(executors)); 83 | } 84 | 85 | void concurrencpp::tests::test_resume_on_ref() { 86 | concurrencpp::runtime runtime; 87 | concurrencpp::executor* executors[4]; 88 | 89 | executors[0] = runtime.thread_executor().get(); 90 | executors[1] = runtime.thread_pool_executor().get(); 91 | executors[2] = runtime.make_worker_thread_executor().get(); 92 | executors[3] = runtime.make_worker_thread_executor().get(); 93 | 94 | std::unordered_set set; 95 | resume_on_many_executors_ref(executors, set).get(); 96 | 97 | assert_equal(set.size(), std::size(executors)); 98 | } 99 | 100 | using namespace concurrencpp::tests; 101 | 102 | int main() { 103 | tester tester("resume_on"); 104 | 105 | tester.add_step("resume_on(nullptr)", test_resume_on_null_executor); 106 | tester.add_step("resume_on - executor was shut down", test_resume_on_shutdown_executor); 107 | tester.add_step("resume_on - executor is shut down after enqueuing", test_resume_on_shutdown_executor_delayed); 108 | tester.add_step("resume_on(std::shared_ptr)", test_resume_on_shared_ptr); 109 | tester.add_step("resume_on(&)", test_resume_on_ref); 110 | 111 | tester.launch_test(); 112 | return 0; 113 | } -------------------------------------------------------------------------------- /test/source/thread_sanitizer/async_lock.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | #include 5 | 6 | void async_increment(concurrencpp::runtime& runtime); 7 | void async_insert(concurrencpp::runtime& runtime); 8 | 9 | int main() { 10 | std::cout << "Starting async_lock test" << std::endl; 11 | 12 | concurrencpp::runtime runtime; 13 | 14 | std::cout << "================================" << std::endl; 15 | std::cout << "async increment" << std::endl; 16 | async_increment(runtime); 17 | 18 | std::cout << "================================" << std::endl; 19 | std::cout << "async insert" << std::endl; 20 | async_insert(runtime); 21 | 22 | std::cout << "================================" << std::endl; 23 | } 24 | 25 | using namespace concurrencpp; 26 | 27 | result incremenet(executor_tag, 28 | std::shared_ptr ex, 29 | async_lock& lock, 30 | size_t& counter, 31 | size_t cycles, 32 | std::chrono::time_point tp) { 33 | 34 | std::this_thread::sleep_until(tp); 35 | 36 | for (size_t i = 0; i < cycles; i++) { 37 | auto lk = co_await lock.lock(ex); 38 | ++counter; 39 | } 40 | } 41 | 42 | result insert(executor_tag, 43 | std::shared_ptr ex, 44 | async_lock& lock, 45 | std::vector& vec, 46 | size_t range_begin, 47 | size_t range_end, 48 | std::chrono::time_point tp) { 49 | 50 | std::this_thread::sleep_until(tp); 51 | 52 | for (size_t i = range_begin; i < range_end; i++) { 53 | auto lk = co_await lock.lock(ex); 54 | vec.emplace_back(i); 55 | } 56 | } 57 | 58 | void async_increment(runtime& runtime) { 59 | async_lock mtx; 60 | size_t counter = 0; 61 | 62 | const size_t worker_count = concurrencpp::details::thread::hardware_concurrency(); 63 | constexpr size_t cycles = 500'000; 64 | 65 | std::vector> workers(worker_count); 66 | for (auto& worker : workers) { 67 | worker = runtime.make_worker_thread_executor(); 68 | } 69 | 70 | std::vector> results(worker_count); 71 | 72 | const auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(4); 73 | 74 | for (size_t i = 0; i < worker_count; i++) { 75 | results[i] = incremenet({}, workers[i], mtx, counter, cycles, deadline); 76 | } 77 | 78 | for (size_t i = 0; i < worker_count; i++) { 79 | results[i].get(); 80 | } 81 | 82 | { 83 | auto lock = mtx.lock(workers[0]).run().get(); 84 | if (counter != cycles * worker_count) { 85 | std::cout << "async_lock test failed, counter != cycles * worker_count, " << counter << " " << cycles * worker_count 86 | << std::endl; 87 | } 88 | } 89 | } 90 | 91 | void async_insert(runtime& runtime) { 92 | async_lock mtx; 93 | std::vector vector; 94 | 95 | const size_t worker_count = concurrencpp::details::thread::hardware_concurrency(); 96 | constexpr size_t cycles = 500'000; 97 | 98 | std::vector> workers(worker_count); 99 | for (auto& worker : workers) { 100 | worker = runtime.make_worker_thread_executor(); 101 | } 102 | 103 | std::vector> results(worker_count); 104 | 105 | const auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(4); 106 | 107 | for (size_t i = 0; i < worker_count; i++) { 108 | results[i] = insert({}, workers[i], mtx, vector, i * cycles, (i + 1) * cycles, deadline); 109 | } 110 | 111 | for (size_t i = 0; i < worker_count; i++) { 112 | results[i].get(); 113 | } 114 | 115 | { 116 | auto lock = mtx.lock(workers[0]).run().get(); 117 | if (vector.size() != cycles * worker_count) { 118 | std::cerr << "async_lock test failed, vector.size() != cycles * worker_count, " << vector.size() 119 | << " != " << cycles * worker_count << std::endl; 120 | } 121 | 122 | std::sort(vector.begin(), vector.end()); 123 | 124 | for (size_t i = 0; i < worker_count * cycles; i++) { 125 | if (vector[i] != i) { 126 | std::cerr << "async_lock test failed, vector[i] != i, " << vector[i] << " != " << i << std::endl; 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/source/thread_sanitizer/fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | concurrencpp::result fibbonacci(concurrencpp::executor_tag, std::shared_ptr tpe, int curr); 6 | int fibbonacci_sync(int i) noexcept; 7 | 8 | int main() { 9 | std::cout << "Starting parallel Fibonacci test" << std::endl; 10 | 11 | const auto fibb_sync = fibbonacci_sync(32); 12 | 13 | concurrencpp::runtime_options opts; 14 | opts.max_cpu_threads = std::thread::hardware_concurrency() * 8; 15 | concurrencpp::runtime runtime(opts); 16 | 17 | const auto fibb = fibbonacci({}, runtime.thread_pool_executor(), 32).get(); 18 | if (fibb != fibb_sync) { 19 | std::cerr << "fibonacci test failed. expected " << fibb_sync << " got " << fibb << std::endl; 20 | std::abort(); 21 | } 22 | 23 | std::cout << "fibonacci(32) = " << fibb << std::endl; 24 | std::cout << "================================" << std::endl; 25 | } 26 | 27 | using namespace concurrencpp; 28 | 29 | result fibbonacci(executor_tag, std::shared_ptr tpe, const int curr) { 30 | if (curr == 1) { 31 | co_return 1; 32 | } 33 | 34 | if (curr == 0) { 35 | co_return 0; 36 | } 37 | 38 | auto fib_1 = fibbonacci({}, tpe, curr - 1); 39 | auto fib_2 = fibbonacci({}, tpe, curr - 2); 40 | 41 | co_return co_await fib_1 + co_await fib_2; 42 | } 43 | 44 | int fibbonacci_sync(int i) noexcept { 45 | if (i == 0) { 46 | return 0; 47 | } 48 | 49 | if (i == 1) { 50 | return 1; 51 | } 52 | 53 | return fibbonacci_sync(i - 1) + fibbonacci_sync(i - 2); 54 | } 55 | -------------------------------------------------------------------------------- /test/source/thread_sanitizer/lazy_fibonacci.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | 5 | concurrencpp::lazy_result lazy_fibbonacci(std::shared_ptr tpe, int curr); 6 | int fibbonacci_sync(int i) noexcept; 7 | 8 | int main() { 9 | std::cout << "Starting lazy Fibonacci test" << std::endl; 10 | 11 | const auto fibb_sync = fibbonacci_sync(32); 12 | 13 | concurrencpp::runtime_options opts; 14 | opts.max_cpu_threads = std::thread::hardware_concurrency() * 8; 15 | concurrencpp::runtime runtime(opts); 16 | 17 | const auto fibb = lazy_fibbonacci(runtime.thread_pool_executor(), 32).run().get(); 18 | if (fibb != fibb_sync) { 19 | std::cerr << "fibonacci test failed. expected " << fibb_sync << " got " << fibb << std::endl; 20 | std::abort(); 21 | } 22 | 23 | std::cout << "fibonacci(32) = " << fibb << std::endl; 24 | std::cout << "================================" << std::endl; 25 | } 26 | 27 | using namespace concurrencpp; 28 | 29 | lazy_result lazy_fibbonacci(std::shared_ptr tpe, const int curr) { 30 | if (curr == 1) { 31 | co_return 1; 32 | } 33 | 34 | if (curr == 0) { 35 | co_return 0; 36 | } 37 | 38 | co_await resume_on(tpe); 39 | 40 | auto fib_1 = lazy_fibbonacci(tpe, curr - 1); 41 | auto fib_2 = lazy_fibbonacci(tpe, curr - 2); 42 | 43 | co_return co_await fib_1 + co_await fib_2; 44 | } 45 | 46 | int fibbonacci_sync(int i) noexcept { 47 | if (i == 0) { 48 | return 0; 49 | } 50 | 51 | if (i == 1) { 52 | return 1; 53 | } 54 | 55 | return fibbonacci_sync(i - 1) + fibbonacci_sync(i - 2); 56 | } 57 | -------------------------------------------------------------------------------- /test/source/thread_sanitizer/matrix_multiplication.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | concurrencpp::result test_matrix_multiplication(std::shared_ptr ex); 9 | 10 | int main() { 11 | std::cout << "Testing parallel matrix multiplication" << std::endl; 12 | 13 | concurrencpp::runtime runtime; 14 | test_matrix_multiplication(runtime.thread_pool_executor()).get(); 15 | 16 | std::cout << "================================" << std::endl; 17 | } 18 | 19 | using namespace concurrencpp; 20 | 21 | using matrix = std::array, 1024>; 22 | 23 | std::unique_ptr make_matrix() { 24 | std::default_random_engine generator; 25 | std::uniform_real_distribution distribution(-5000.0, 5000.0); 26 | auto mtx_ptr = std::make_unique(); 27 | auto& mtx = *mtx_ptr; 28 | 29 | for (size_t i = 0; i < 1024; i++) { 30 | for (size_t j = 0; j < 1024; j++) { 31 | mtx[i][j] = distribution(generator); 32 | } 33 | } 34 | 35 | return mtx_ptr; 36 | } 37 | 38 | void test_matrix(const matrix& mtx0, const matrix& mtx1, const matrix& mtx2) { 39 | for (size_t i = 0; i < 1024; i++) { 40 | for (size_t j = 0; j < 1024; j++) { 41 | 42 | auto res = 0.0; 43 | 44 | for (size_t k = 0; k < 1024; k++) { 45 | res += mtx0[i][k] * mtx1[k][j]; 46 | } 47 | 48 | if (mtx2[i][j] != res) { 49 | std::cerr << "matrix multiplication test failed. expected " << res << " got: " << mtx2[i][j] << "at matrix position[" 50 | << i << "," << j << std::endl; 51 | } 52 | } 53 | } 54 | } 55 | 56 | result do_multiply(executor_tag, 57 | std::shared_ptr executor, 58 | const matrix& mtx0, 59 | const matrix& mtx1, 60 | size_t line, 61 | size_t col) { 62 | auto res = 0.0; 63 | for (size_t i = 0; i < 1024; i++) { 64 | res += mtx0[line][i] * mtx1[i][col]; 65 | } 66 | 67 | co_return res; 68 | }; 69 | 70 | result test_matrix_multiplication(std::shared_ptr ex) { 71 | auto mtx0_ptr = make_matrix(); 72 | auto mtx1_ptr = make_matrix(); 73 | auto mtx2_ptr = std::make_unique(); 74 | 75 | matrix& mtx0 = *mtx0_ptr; 76 | matrix& mtx1 = *mtx1_ptr; 77 | matrix& mtx2 = *mtx2_ptr; 78 | 79 | std::vector> results; 80 | results.reserve(1024 * 1024); 81 | 82 | for (size_t i = 0; i < 1024; i++) { 83 | for (size_t j = 0; j < 1024; j++) { 84 | results.emplace_back(do_multiply({}, ex, mtx0, mtx1, i, j)); 85 | } 86 | } 87 | 88 | for (size_t i = 0; i < 1024; i++) { 89 | for (size_t j = 0; j < 1024; j++) { 90 | mtx2[i][j] = co_await results[i * 1024 + j]; 91 | } 92 | } 93 | 94 | test_matrix(mtx0, mtx1, mtx2); 95 | } 96 | -------------------------------------------------------------------------------- /test/source/thread_sanitizer/quick_sort.cpp: -------------------------------------------------------------------------------- 1 | #include "concurrencpp/concurrencpp.h" 2 | 3 | #include 4 | #include 5 | 6 | concurrencpp::result quick_sort(concurrencpp::executor_tag, 7 | std::shared_ptr tp, 8 | int* a, 9 | int lo, 10 | int hi); 11 | 12 | int main() { 13 | std::cout << "Starting parallel quick sort test" << std::endl; 14 | 15 | concurrencpp::runtime_options opts; 16 | opts.max_cpu_threads = std::thread::hardware_concurrency() * 8; 17 | concurrencpp::runtime runtime(opts); 18 | 19 | ::srand(::time(nullptr)); 20 | 21 | std::vector array(8'000'000); 22 | for (auto& i : array) { 23 | i = ::rand() % 100'000; 24 | } 25 | 26 | quick_sort({}, runtime.thread_pool_executor(), array.data(), 0, array.size() - 1).get(); 27 | 28 | const auto is_sorted = std::is_sorted(array.begin(), array.end()); 29 | if (!is_sorted) { 30 | std::cerr << "Quick sort test failed: array is not sorted." << std::endl; 31 | std::abort(); 32 | } 33 | 34 | std::cout << "================================" << std::endl; 35 | } 36 | 37 | using namespace concurrencpp; 38 | 39 | /* 40 | 41 | algorithm partition(A, lo, hi) is 42 | pivot := A[(hi + lo) / 2] 43 | i := lo - 1 44 | j := hi + 1 45 | loop forever 46 | do 47 | i := i + 1 48 | while A[i] < pivot 49 | do 50 | j := j - 1 51 | while A[j] > pivot 52 | if i ≥ j then 53 | return j 54 | swap A[i] with A[j] 55 | */ 56 | 57 | int partition(int* a, int lo, int hi) { 58 | const auto pivot = a[(hi + lo) / 2]; 59 | auto i = lo - 1; 60 | auto j = hi + 1; 61 | 62 | while (true) { 63 | do { 64 | ++i; 65 | } while (a[i] < pivot); 66 | 67 | do { 68 | --j; 69 | } while (a[j] > pivot); 70 | 71 | if (i >= j) { 72 | return j; 73 | } 74 | 75 | std::swap(a[i], a[j]); 76 | } 77 | } 78 | 79 | /* 80 | algorithm quicksort(A, lo, hi) is 81 | if lo < hi then 82 | p := partition(A, lo, hi) 83 | quicksort(A, lo, p) 84 | quicksort(A, p + 1, hi) 85 | 86 | */ 87 | result quick_sort(executor_tag, std::shared_ptr tp, int* a, int lo, int hi) { 88 | if (lo >= hi) { 89 | co_return; 90 | } 91 | 92 | const auto p = partition(a, lo, hi); 93 | auto res0 = quick_sort({}, tp, a, lo, p); 94 | auto res1 = quick_sort({}, tp, a, p + 1, hi); 95 | 96 | co_await res0; 97 | co_await res1; 98 | } 99 | -------------------------------------------------------------------------------- /test/source/utils/object_observer.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/object_observer.h" 2 | #include "concurrencpp/concurrencpp.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace concurrencpp::tests::details { 8 | class object_observer_state { 9 | 10 | private: 11 | mutable std::mutex m_lock; 12 | mutable std::condition_variable m_condition; 13 | std::unordered_map m_execution_map; 14 | size_t m_destruction_count; 15 | size_t m_execution_count; 16 | 17 | public: 18 | object_observer_state() : m_destruction_count(0), m_execution_count(0) {} 19 | 20 | size_t get_destruction_count() const noexcept { 21 | std::unique_lock lock(m_lock); 22 | return m_destruction_count; 23 | } 24 | 25 | size_t get_execution_count() const noexcept { 26 | std::unique_lock lock(m_lock); 27 | return m_execution_count; 28 | } 29 | 30 | std::unordered_map get_execution_map() const noexcept { 31 | std::unique_lock lock(m_lock); 32 | return m_execution_map; 33 | } 34 | 35 | bool wait_execution_count(size_t count, std::chrono::milliseconds timeout) { 36 | std::unique_lock lock(m_lock); 37 | return m_condition.wait_for(lock, timeout, [count, this] { 38 | return count == m_execution_count; 39 | }); 40 | } 41 | 42 | void on_execute() { 43 | const auto this_id = ::concurrencpp::details::thread::get_current_virtual_id(); 44 | 45 | { 46 | std::unique_lock lock(m_lock); 47 | ++m_execution_count; 48 | ++m_execution_map[this_id]; 49 | } 50 | 51 | m_condition.notify_all(); 52 | } 53 | 54 | bool wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { 55 | std::unique_lock lock(m_lock); 56 | return m_condition.wait_for(lock, timeout, [count, this] { 57 | return count == m_destruction_count; 58 | }); 59 | } 60 | 61 | void on_destroy() { 62 | { 63 | std::unique_lock lock(m_lock); 64 | ++m_destruction_count; 65 | } 66 | 67 | m_condition.notify_all(); 68 | } 69 | }; 70 | } // namespace concurrencpp::tests::details 71 | 72 | using concurrencpp::tests::testing_stub; 73 | using concurrencpp::tests::object_observer; 74 | using concurrencpp::tests::value_testing_stub; 75 | 76 | testing_stub& testing_stub::operator=(testing_stub&& rhs) noexcept { 77 | if (this == &rhs) { 78 | return *this; 79 | } 80 | 81 | if (static_cast(m_state)) { 82 | m_state->on_destroy(); 83 | } 84 | 85 | m_state = std::move(rhs.m_state); 86 | return *this; 87 | } 88 | 89 | void testing_stub::operator()() noexcept { 90 | if (static_cast(m_state)) { 91 | m_state->on_execute(); 92 | } 93 | } 94 | 95 | value_testing_stub& value_testing_stub::operator=(value_testing_stub&& rhs) noexcept { 96 | testing_stub::operator=(std::move(rhs)); 97 | return *this; 98 | } 99 | 100 | size_t value_testing_stub::operator()() noexcept { 101 | testing_stub::operator()(); 102 | return m_expected_return_value; 103 | } 104 | 105 | object_observer::object_observer() : m_state(std::make_shared()) {} 106 | 107 | testing_stub object_observer::get_testing_stub() noexcept { 108 | return {m_state}; 109 | } 110 | 111 | value_testing_stub object_observer::get_testing_stub(int value) noexcept { 112 | return {m_state, value}; 113 | } 114 | 115 | value_testing_stub object_observer::get_testing_stub(size_t value) noexcept { 116 | return {m_state, value}; 117 | } 118 | 119 | bool object_observer::wait_execution_count(size_t count, std::chrono::milliseconds timeout) { 120 | return m_state->wait_execution_count(count, timeout); 121 | } 122 | 123 | bool object_observer::wait_destruction_count(size_t count, std::chrono::milliseconds timeout) { 124 | return m_state->wait_destruction_count(count, timeout); 125 | } 126 | 127 | size_t object_observer::get_destruction_count() const noexcept { 128 | return m_state->get_destruction_count(); 129 | } 130 | 131 | size_t object_observer::get_execution_count() const noexcept { 132 | return m_state->get_execution_count(); 133 | } 134 | 135 | std::unordered_map object_observer::get_execution_map() const noexcept { 136 | return m_state->get_execution_map(); 137 | } 138 | 139 | testing_stub::~testing_stub() noexcept { 140 | if (static_cast(m_state)) { 141 | m_state->on_destroy(); 142 | } 143 | } 144 | --------------------------------------------------------------------------------