Skip to content

Commit

Permalink
upgrade pipeline (#61)
Browse files Browse the repository at this point in the history
* upgrade pipeline

* add doc for babylon::DepositBox

* add doc for coroutine
  • Loading branch information
oathdruid authored Oct 14, 2024
1 parent 0c51aeb commit 2a35528
Show file tree
Hide file tree
Showing 19 changed files with 360 additions and 13 deletions.
6 changes: 4 additions & 2 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
common --registry=https://bcr.bazel.build
common --registry=file://%workspace%/registry

build --cxxopt=-std=c++20
build --cxxopt=-faligned-new

# Include attribute is not properly set in abseil-cpp.
# Enable external_include_paths globally make them include with -isystem.
test --//:werror --features external_include_paths

# SwissMemoryResource's patch violate odr rule. But still keep same struct with same defination and same size
Expand All @@ -11,7 +13,7 @@ test --test_env=ASAN_OPTIONS=detect_odr_violation=1

# There are bugs about -fsanitize=address and -Wuninitialized
# as mentioned in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105616
test:asan --features=asan --copt=-Wno-maybe-uninitialized --copt=-Wno-unknown-warning-option
test:asan --features=asan

test:arenastring --//test/proto:arenastring
test:mutable-donated-string --config=arenastring --copt=-DGOOGLE_PROTOBUF_MUTABLE_DONATED_STRING=1
26 changes: 19 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ jobs:
fail-fast: false
matrix:
compiler: [{name: gcc, flag: --action_env=CC=gcc-14}, {name: clang, flag: --action_env=CC=clang-18}]
std: [{name: c++20}, {name: c++14, flag: --cxxopt=-std=c++14 --cxxopt=-faligned-new}, {name: c++14-coroutine, flag: --cxxopt=-faligned-new --cxxopt=-fconcepts --cxxopt=-fcoroutines}]
std: [{name: c++20, flag: --cxxopt=-std=c++20}, {name: c++14, flag: --cxxopt=-std=c++14}]
stdlib: [{name: stdlibc++}, {name: libc++, flag: --cxxopt=-stdlib=libc++ --linkopt=-stdlib=libc++}]
feature: [{name: asan, flag: --config=asan}, {name: tsan, flag: --features=tsan}]
exclude:
- compiler: {name: gcc}
stdlib: {name: libc++}
- compiler: {name: clang}
std: {name: c++14-coroutine}
- std: {name: c++14}
feature: {name: tsan}
- std: {name: c++14-coroutine}
feature: {name: tsan}
runs-on: ubuntu-latest
name: basic-${{matrix.compiler.name}}-${{matrix.std.name}}-${{matrix.stdlib.name}}-${{matrix.feature.name}}
steps:
Expand Down Expand Up @@ -73,6 +69,22 @@ jobs:
path: bazel-disk
key: bazel-disk-arenastring-${{matrix.compiler.name}}-${{matrix.stdlib.name}}-${{matrix.mutable.name}}-${{github.sha}}

cpp14-coroutine:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache/restore@v4
with:
path: bazel-disk
key: bazel-disk-c++14-coroutine-${{github.sha}}
restore-keys: bazel-disk-c++14-coroutine-
- run: bazel test --disk_cache=bazel-disk --verbose_failures --test_output=errors --action_env=CC=gcc-12 --cxxopt=-std=c++14 --cxxopt=-fcoroutines --cxxopt=-fconcepts test/...
- uses: actions/cache/save@v4
if: always()
with:
path: bazel-disk
key: bazel-disk-c++14-coroutine-${{github.sha}}

aarch64:
runs-on: ubuntu-latest
steps:
Expand All @@ -83,10 +95,10 @@ jobs:
key: bazel-disk-aarch64-${{github.sha}}
restore-keys: bazel-disk-aarch64-
- run: sudo apt update
- run: sudo apt install g++-12-aarch64-linux-gnu
- run: sudo apt install g++-14-aarch64-linux-gnu
- run: sudo apt install qemu-user
- run: sudo ln -s /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1 /lib/
- run: bazel test --disk_cache=bazel-disk --verbose_failures --test_output=errors --platforms='@cross_config_toolchain//:cross' --action_env=CROSS_CC=/usr/bin/aarch64-linux-gnu-gcc-12 --features=-default_link_flags --test_env=LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib -- test/... -test/logging:test_log_statically
- run: bazel test --disk_cache=bazel-disk --verbose_failures --test_output=errors --platforms='@cross_config_toolchain//:cross' --action_env=CROSS_CC=/usr/bin/aarch64-linux-gnu-gcc-14 --features=-default_link_flags --test_env=LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib -- test/... -test/logging:test_log_statically
- uses: actions/cache/save@v4
if: always()
with:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Babylon也支持使用[CMake](https://cmake.org)进行构建,并支持通过[f
- [:anyflow](docs/anyflow/index.md)
- [:application_context](docs/application_context.md)
- [:concurrent](docs/concurrent/index.md)
- [:coroutine](docs/coroutine)
- [:executor](docs/executor.md)
- [:future](docs/future.md)
- [:logging](docs/logging/index.md)
Expand Down
2 changes: 1 addition & 1 deletion copts.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
BABYLON_GCC_COPTS = ['-Wall', '-Wextra']
BABYLON_CLANG_COPTS = ['-faligned-new', '-Weverything', '-Wno-unknown-warning-option',
BABYLON_CLANG_COPTS = ['-Weverything', '-Wno-unknown-warning-option',
# 不保持老版本c++语法兼容
'-Wno-c++98-compat-pedantic',
# Boost Preprocessor中大量使用
Expand Down
57 changes: 57 additions & 0 deletions docs/concurrent/deposit_box.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# deposit_box

## 原理

有时我们需要一种多个调用者动态竞争完成相同一段逻辑的设计模式,典型类似实现timeout动作(一个有效,一个无效)或者backup request动作(两个有效,先到先得);从原理上,这种模式需要的机制和[std::call_once](https://en.cppreference.com/w/cpp/thread/call_once)非常类似,但是会有以下几个不同点:
- 后来者并不需要等待执行者完成,单纯放弃自己的运行即可;
- 由于后来者原理上不需要任何执行动作和结果,执行者可以更早释放资源进行复用,相应地后来者需要确保不会修改可能已经被复用的资源;

![](images/deposit_box.png)

采用[IdAllocator](id_allocator.md)[ConcurrentVector](vector.md)组织实际的数据,实现聚集存储和基于序号的快速访问;每一个轮次中的take动作通过对版本号的CAS自增实现归属权竞争,后来者除CAS动作外不碰触数据部分;版本号自身同样存储在[ConcurrentVector](vector.md)的槽位内部,确保后来者的CAS动作本身合法,而版本号本身的单调递增特性排除了后来者的ABA问题;

## 用法示例

### DepositBox

```c++
#include <babylon/concurrent/deposit_box.h>

using ::babylon::DepositBox;

// 仅支持通过全局单例使用
auto& box = DepositBox<Item>::instance();

// 分配一个槽位,并在其中通过Item(...)构造元素,返回id用于未来竞争取回这个元素
// 并发的多个emplace动作是线程安全的
auto id = box.emplace(...);

{
// 取回操作可以对同一个id竞争执行,是线程安全的
auto accessor = box.take(id);
// 通过accessor是否非空可以判定是否是第一个获取到所有权的访问者
// 非空accessor可以进一步用于访问元素
if (accessor) {
accessor->item_member_function(...);
some_function_use_item(*accessor);
}
} // accessor析构时释放槽位,元素指针不再可用

///////////////////////////// 高级用法 /////////////////////////////////////

// 非RAII模式,直接返回元素指针Item*
auto item = box.take_released(id);
if (item) {
item->item_member_function(...);
some_function_use_item(*item);
}
// 不再需要访问Item*时需要主动释放所有权
box.finish_released(id);

// 不校验和操作版本号部分,直接取得id对应槽位内的元素指针,因此不能和take动作安全并发
// 在一些不能通过构造函数就完成元素准备的场景,可以在分配槽位后进一步增量操作元素
// 但是需要使用者确保此时id还未交给竞争访问者执行take动作
auto item = box.unsafe_get(id);
item->item_member_function(...);
some_function_use_item(*item);
```
Binary file added docs/concurrent/images/deposit_box.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/concurrent/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [bounded_queue](bounded_queue.md)
- [counter](counter.md)
- [deposit_box](deposit_box.md)
- [epoch](epoch.md)
- [execution_queue](execution_queue.md)
- [garbage_collector](garbage_collector.md)
Expand Down
21 changes: 21 additions & 0 deletions docs/coroutine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# coroutine

## 原理

基于[C++20](https://en.cppreference.com/w/cpp/20)[coroutine](https://en.cppreference.com/w/cpp/language/coroutines)标准,实现的一套协程机制;按照标准,一个协程函数除了内部包含`co_xxx`关键字语句之外,返回值也有一系列特殊要求;要求整体可以分为两个大类,一类用来支持单一返回值协程,返回值一般称为task;另一类用来支持多次返回值协程,一般被称为generator;

![](images/promise.png)
![](images/awaitable.png)

[coroutine](https://en.cppreference.com/w/cpp/language/coroutines)标准并非直接定义了协程机制,而是更为抽象地统一了协程机制的API部分,并将细粒度的SPI部分分离到用户不可见的部分;最终通过用户 -> API -> 编译器 -> SPI -> 协程机制来进行运转;整体工作模式为
- 最终用户通过统一关键字操作符表达语义,例如`co_await``co_return`
- 最终用户通过协程函数返回类型,表达实际对接的协程机制
- 编译器按照操作符语义,按标准分阶段完成协程的中断和恢复
- 编译器在中断和恢复前后的若干标准点位,调用实际协程提供框架的相应功能

## 子模块文档

- [task](task.md)
- [future_awaitable](future_awaitable.md)
- [cancellable](cancellable.md)
- [futex](futex.md)
42 changes: 42 additions & 0 deletions docs/coroutine/cancellable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# cancellable

## 原理

标准[coroutine](https://en.cppreference.com/w/cpp/language/coroutines)机制中的`co_await`相当于[std::future::get](https://en.cppreference.com/w/cpp/thread/future/get)方法,即等待目标awaitable完成,并在之后才能恢复执行;但有时需要让等待具备提前结束的能力,最为典型的场景是timeout这类场景;

![](images/cancellable.png)

实现上Cancellable包装一个常规的awaitable,并通过插入一个proxy awaitable实现取消支持;一方面proxy awaitable传递`co_await`到包装的awaitable,并最终传导恢复执行动作到发起`co_await`的原始协程;另一方面,proxy awaitable并不将待恢复的协程句柄存在本地,而是存放到[DepositBox](../concurrent/deposit_box.md)中,并同时传输给注册的timer等执行取消动作的触发源;当包装的awaitable发起恢复,或者取消触发源发起取消时,会竞争[DepositBox](../concurrent/deposit_box.md)中存放的协程句柄,并由胜出方执行恢复动作;

## 用法示例

```c++
#include "babylon/coroutine/task.h"
#include "babylon/coroutine/cancellable.h"

using ::babylon::coroutine::Task;
using ::babylon::coroutine::Cancellable;

using Cancellation = typename Cancellable<A>::Cancellation;

Task<...> some_coroutine(...) {
...
// 包装原始awaitable称为Cancellable
auto optional_value = co_await Cancellable<A>(::std::move(a)).on_suspend(
// 通过回调函数在协程挂起后接收相应的取消句柄
[&](Cancellation cancel) {
// 典型操作是把cancel句柄注册到某种timer机制,并在指定时间后调用cancel()发起取消
// 从回调被执行开始,cancel就可用了,甚至在回调内部也可以直接发起cancel(),虽然一般这并没有什么意义
on_timer(cancel, 100ms);
}
);
// 如果a先完成,则返回非空值可供操作
if (optional_value) {
optional_value->item_member_function(...);
some_function_use_item(*optional_value);
} else {
// 收到空值表示取消动作先一步发起
}
...
}
```
48 changes: 48 additions & 0 deletions docs/coroutine/futex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# futex

## 原理

标准[coroutine](https://en.cppreference.com/w/cpp/language/coroutines)机制中的`co_await`基本对标了[std::future](https://en.cppreference.com/w/cpp/thread/future)并行同步模式;但更多复杂的同步模式例如[std::mutex](https://en.cppreference.com/w/cpp/thread/mutex)或者[std::condition_variable](https://en.cppreference.com/w/cpp/thread/condition_variable)的支持可以统一通过类似[futex(2)](https://man7.org/linux/man-pages/man2/futex.2.html)的机制统一支持;

![](images/futex.png)

实现上通过每个futex实例伴随一个`std::mutex`来实现值检测和等待回调链串联原子性;通过[DepositBox](../concurrent/deposit_box.md)实现取消和唤醒的唯一性;

## 用法示例

```c++
#include "babylon/coroutine/task.h"
#include "babylon/coroutine/futex.h"

using ::babylon::coroutine::Task;
using ::babylon::coroutine::Futex;

using Cancellation = Futex::Cancellation;

// Futex构造时内部值为0
Futex futex;

// 读写futex内部值
futex.value() = ...
// 原子式读写futex内部值
futex.atomic_value().xxxx(...);

Task<...> some_coroutine(...) {
...
// 原子检测内部值是否为expected_value,是则挂起some_coroutine,否则直接继续执行
co_await futex.wait(expected_value).on_suspend(
// 通过回调函数在协程挂起后接收相应的取消句柄
// 注意,如果协程未经历挂起,回调不会被调用
[&](Cancellation cancel) {
// 典型操作是把cancel句柄注册到某种timer机制,并在指定时间后调用cancel()发起取消
// 从回调被执行开始,cancel就可用了,甚至在回调内部也可以直接发起cancel(),虽然一般这并没有什么意义
on_timer(cancel, 100ms);
}
);
// 有种可能执行到这里
// 1. 未满足expected_value
// 2. 挂起后futex.wake_one或者futex.wake_all被调用
// 3. 挂起后cancel()被调用
...
}
```
39 changes: 39 additions & 0 deletions docs/coroutine/future_awaitable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# future_awaitable

## 原理

[Future](../future.md)本身的回调机制使其非常适合成为一个协程的awaitable,一定程度上也是因为协程内部本身也采用future/promise设计模式的原因;对于[Future](../future.md)进行awaitable的包装提供了两个不同版本,FutureAwaitable为独占模式,即一个Future对应单一Awaiter,一般情况下独占模式可以满足大多数需求;另一个版本是SharedFutureAwaitable共享模式,同一个future可以对应多个Awaiter,完成时同时恢复多个协程;

## 用法示例

```c++
#include "babylon/coroutine/task.h"
#include "babylon/future.h"

using ::babylon::coroutine::Task;
using ::babylon::Future;

::babylon::Future<T> future = ...

// 独占模式,value会从future中被move出来
Task<...> some_coroutine(...) {
...
// 执行后当前线程会挂起some_coroutine,可能会开始执行其他排队中的任务
T value = co_await ::std::move(future);
// 当promise.set_value(...)被执行后会恢复执行some_coroutine
// some_coroutine依然会回到原先其绑定的executor执行
// 采用co_await Future&&最终返回值会得到T&&,需要采用T本体接收
...
}

// 共享模式,value会以引用行驶从future中被暴露出来,需要共享使用方考虑修改安全性
Task<...> some_coroutine(...) {
...
// 执行后当前线程会挂起some_coroutine,可能会开始执行其他排队中的任务
T& value = co_await future;
// 当promise.set_value(...)被执行后会恢复执行some_coroutine
// some_coroutine依然会回到原先其绑定的executor执行
// 采用co_await Future&最终返回值会得到T&,此时T指向了内部的本体,生命周期随future控制
...
}
```
Binary file added docs/coroutine/images/awaitable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/coroutine/images/cancellable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/coroutine/images/futex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/coroutine/images/promise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2a35528

Please sign in to comment.