失敗したrequireオペレーターをリトライする時の罠っぽい挙動

# 前提
先にこちらの記事の内容を理解しておくことをお勧めします。
https://qiita.com/shiozaki/items/b3aaff926b7b601b4f86

# 概要
requireオペレーターが書かれたワークフローが失敗した時に、リトライする手順によっては意図せずワークフローを二重に起動することになります。

# 再現手順
以下のワークフローを用意します。

``` workflow1.dig
+step1:
  require>: workflow2
```

```workflow2.dig
+step2:
  sh>: exit 1 # this is a bug.
```

workflow1がworkflow2をrequireしています。
workflow2にはバグ( `exit 1` )があり、shオペレーターが失敗します。

この状態で、workflow1を実行させてみます。
workflow1がworkflow2を呼び出しますが、workflow2のstep2で失敗し、連鎖的にworkflow1のstep1が失敗しています。

```bash:別のシェルで起動しておく
$ digdag server -m -O /tmp/digdag_log
```

```
$ digdag push sample
$ digdag start sample workflow1 --session '2019-01-01 00:00:00'
```

```log:log
2019-03-24 19:16:03 +0900 [INFO] (XNIO-1 task-19): Starting a new session project id=1 workflow name=workflow1 session_time=2019-01-01T00:00:00+00:00
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow1+step1): require>: workflow2
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow1+step1): Starting a new session project id=1 workflow name=workflow2 session_time=2019-01-01T00:00:00+00:00
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow2+step2): sh>: exit 1
2019-03-24 19:16:04 +0900 [ERROR] (0044@[0:sample]+workflow2+step2): Task failed with unexpected error: Command failed with code 1
java.lang.RuntimeException: Command failed with code 1
	at io.digdag.standards.operator.ShOperatorFactory$ShOperator.runTask(ShOperatorFactory.java:143)
	at io.digdag.util.BaseOperator.run(BaseOperator.java:35)
	at io.digdag.core.agent.OperatorManager.callExecutor(OperatorManager.java:312)
	at io.digdag.core.agent.OperatorManager.runWithWorkspace(OperatorManager.java:254)
	at io.digdag.core.agent.OperatorManager.lambda$runWithHeartbeat$2(OperatorManager.java:137)
	at io.digdag.core.agent.ExtractArchiveWorkspaceManager.withExtractedArchive(ExtractArchiveWorkspaceManager.java:77)
	at io.digdag.core.agent.OperatorManager.runWithHeartbeat(OperatorManager.java:135)
	at io.digdag.core.agent.OperatorManager.run(OperatorManager.java:119)
	at io.digdag.core.agent.MultiThreadAgent.lambda$null$0(MultiThreadAgent.java:127)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
2019-03-24 19:16:04 +0900 [INFO] (0044@[0:sample]+workflow2^failure-alert): type: notify
2019-03-24 19:16:05 +0900 [INFO] (0044@[0:sample]+workflow1+step1): require>: workflow2
2019-03-24 19:16:05 +0900 [ERROR] (0044@[0:sample]+workflow1+step1): Task +workflow1+step1 failed.
Dependent workflow failed. Session id: 2, attempt id: 2
2019-03-24 19:16:06 +0900 [INFO] (0044@[0:sample]+workflow1^failure-alert): type: notify
```

バグを修正し、digdag pushします。

```workflow2.dig(fixed)
+step2:
  sh>: exit 0 # fixed the bug
```

```bash
digdag push sample
```

次にリトライをしますが、この時に、sessionを見ると2つのセッションがあることが分かります。
セッションが2つあるので、どちらから先にリトライするべきか悩みます。
リトライする順序によっては同じワークフローが二重に実行される恐れがあるので、注意が必要です。
このようなケースの場合は、 **requireオペレーターが書かれている側のワークフローだけをリトライすると、2つのワークフローがそれぞれ1回だけ実行されます。**

```
$ digdag sessions
2019-03-24 19:20:27 +0900: Digdag v0.9.33
Sessions:
  session id: 1
  attempt id: 1
  uuid: 11164087-9f57-4ece-85fe-8530d853c9fb
  project: sample
  workflow: workflow1
  session time: 2019-01-01 00:00:00 +0000
  retry attempt name:
  params: {}
  created at: 2019-03-24 19:16:03 +0900
  kill requested: false
  status: error

  session id: 2
  attempt id: 2
  uuid: afef1840-53ab-4181-9380-a13d62ed64f0
  project: sample
  workflow: workflow2
  session time: 2019-01-01 00:00:00 +0000
  retry attempt name:
  params: {}
  created at: 2019-03-24 19:16:04 +0900
  kill requested: false
  status: error
```

以下、workflow2が二重起動してしまう手順です。
workflow2側のセッションを先にリトライします。(1回目)

```
$ digdag retry 2 --latest-revision --resume
```

この時点でセッション一覧を見ると、workflow1側のセッションはfailuerのままです。
ですので、次にそちらのセッションもリトライします。

```
$ digdag retry 1 --latest-revision --resume
```

この時、workflow1側は既にsuccessなセッションがあるにもかかわらず、再度実行されてしまいます。(2回目)
なお、この挙動は `--resume` オプションではなく `--all` オプションを指定した場合や、web UIの`RETRY ALL` ボタンや `RETRY FAILED` ボタンを押した場合も同様です。

# まとめ
失敗したrequireをリトライする時にはどちらのワークフローからリトライするかによって挙動が変わる。

requireオペレーターが書かれた側のワークフローをリトライする
→ 引数に指定されたワークフローも連鎖的に起動され、どちらのワークフローも1回ずつ実行される

requireオペレーターの引数に指定された側のワークフローをリトライし、その後にrequireオペレーターが書かれた側のワークフローをリトライする
→ requireオペレーターの引数に指定された側のワークフローは2回、requireオペレーターが書かれた側のワークフローは1回実行される

多くのケースでは前者の挙動が望ましいケースになるかと思います。