---
# System prepended metadata

title: OneShotEvent in Chromium
tags: [Chromium-General, elkurin-daily-note]

---

# OneShotEvent in Chromium

[OneShotEvent](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.h;l=32;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) はたった一度しか発生しないことを期待されているイベントを記録するためのクラス。

こんな感じで使う。
```cpp=
std::unique_ptr<base::OneShotEvent> on_ready_;

void AppServiceProxyAsh::Initialize() {
  ...;
  on_ready_ = std::make_unique<base::OneShotEvent>();
  ...;
}

void AppServiceProxyAsh::OnAppsReady() {
  ...;
  CHECK(on_ready_);
  on_ready_->Signal();
}
```

この例ではInitialize後にSignalが一度まで呼ばれることを保証している。  
他にも、Signalが飛んできたあとにTaskを走らせるという使い方もできる。  
単純だけど助かるお便利クラス。  
実際に中身を見ながらできることを確認しよう。

## 中身
[Signal](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=62;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) を呼ぶと `signaled_` の値をフリップする。  
`signaled_`は最初はfalseで初期化されており、Signalが呼ばれる際には`CHECK(!signaled_)`で確認されている通りfalseのままでないといけない。つまり、Signalが呼ばれて一度フリップされたあとに呼ばれてはいけないので、二度以上のコールをブロックしてる。

その後`tasks_`に積まれているTaskInfoを走らせている。

```cpp=
for (TaskInfo& task : moved_tasks) {
  if (task.delay.is_zero())
    task.runner->PostTask(task.from_here, std::move(task.task));
  else
    task.runner->PostDelayedTask(task.from_here, std::move(task.task),
                                 task.delay);
}
```
[TaskInfo](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=18;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) は行うタスクを示す`OnceClosure`とそれを走らせる`SignleThreadTaskRunner`、そして`delay`とLocationを受け取る。  


これらのTaskは[Post](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=49;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e), [PostDelay](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=55;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e) から追加することができる。  
Postされたタスクは`signal_`がtrueなら即走らせ(DelayedTaskならdelay後に走らせ)、まだ`signal_`がfalseならvectorでリストを持っている[`tasks_`](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.h;l=101;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e)に保存する。

### Task が走っている最中のPostについて
Signalの中で`tasks_`に登録されているOnceClosureをすべて走らせている。  
ところで、Taskを走らせる前にstd::swapで`tasks_`の中身をもうひとつのvector objectに移動している。
```cpp=
std::vector<TaskInfo> moved_tasks;
std::swap(moved_tasks, tasks_);
```
これは、タスクを走らせている間に新しいタスクが追加されるケースを考慮してのこと。  
最後にこんなDCHECKがある。
```cpp=
DCHECK(tasks_.empty()) << "No new tasks should be added during task running!";
```
`tasks_`を走らせ始める前に`signaled_`をフリップしているので、もしTaskを走らせている間にPostが飛んできたら[PostImpl](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=88;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e)の中で`is_signaled()`がtrueの分岐に入り速攻タスクは走る。なのでリストの中には追加されない。

このことからわかるとおり、必ずしもOncShotEventにPostする順にTaskRunnerに追加されるわけではない。  

TaskInfoの各メンバに立ち返ると、TaskRunnerはSingleThreadTaskRunnerしか許可されておらず、SequencedTaskRunnerという順にタスクを行えるやつは渡せないようになっている。  
[コード内コメント](https://source.chromium.org/chromium/chromium/src/+/main:base/one_shot_event.cc;l=76-77;drc=63e1f9974bc57b0ca12d790b2a73e5ba7f5cec6e)でも言及している通りOneShotEventに投げるタスクは順不同。

## Note
OneShotEventでは `std::vector<TaskInfo> tasks_`にmutable修飾子がついている。  
mutableはconstで宣言されているオブジェクトの中でもconstを無視して値を変えることができるやつ。  
今回`tasks_`がmutableでマークされているのは、OneShotEventのインスタンス自体が変更されないことを保証したいが、そこにpending tasksを乗せたとしてもlogically constだから。  
使う側から見ると、staticなタスクを投げているだけの場合それは論理的にはconstマークしてよいはずだから、mutableマークにしてconstにできるケースを増やしている。  
`tasks_`が変更されないことを保証したいケースはなさそうなのでよさそう。