SinatraでCtrl+Cを2回押さないと停止しない問題

# 事の始まり
( ^o^)「Sinatraでメモアプリ作るぞ〜〜」

( ^o^)「起動を確認して…大丈夫だな。Ctrl+Cで終了するぞ」

(; ^o^)__「…あれ！？一回押しただけじゃ終わらない！？」__

# 結論（原因）
- スーパークラスの指定を間違えていた
- READMEをしっかり読んでいなかった
    - 思い込みでコードを書いていた
- GitHub（ソースコードやissue、使用例）を読んでいなかった
- 開発のためにrequireしていたものが影響を与えていた

## 開発環境
Ruby 3.1.2p20
Sinatra 2.2.0

# Sinatraとは？
> Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort:
引用元： [Sinatra](http://sinatrarb.com/)

簡単にRubyでWebアプリケーションを作ることができる[DSL](https://ja.wikipedia.org/wiki/%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E5%9B%BA%E6%9C%89%E8%A8%80%E8%AA%9E)だそうです。
使用してみた感じは、「Railsより簡単に扱える」「シンプルなWebアプリケーションを作るのに向いている」という感触でした。
[READMEの日本語版](http://sinatrarb.com/intro-ja.html)が用意されているので「英語わからん」って人でもすぐに触ることができます！

# 今回の問題点
タイトルにもあるとおり、Sinatraを終了させるのにCtrl+Cを一回押しても終わらない（２回押したら終了する。）という状態でした。

## ログと画面
`bundle exec ruby app.rb`でSinatraを起動してから終了するまでのログとスクリーンショットを貼ります。
いずれも `localhost:4567` です。

### 起動直後
正常にアプリが起動します。

```
$ bundle exec ruby app.rb
[2022-07-04 15:32:57] INFO  WEBrick 1.7.0
[2022-07-04 15:32:57] INFO  ruby 3.1.2 (2022-04-12) [arm64-darwin21]
== Sinatra (v2.2.0) has taken the stage on 4567 for development with backup from WEBrick
[2022-07-04 15:32:57] INFO  WEBrick::HTTPServer#start: pid=68679 port=4567
::1 - - [04/Jul/2022:15:32:58 +0900] "GET / HTTP/1.1" 200 3873 0.0281
::1 - - [04/Jul/2022:15:32:58 JST] "GET / HTTP/1.1" 200 3873
- -> /
```

![スクリーンショット 2022-07-04 15.33.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/5139b050-0112-a0ef-e9e5-d34da3027c87.png)


### Ctrl + Cを一度押した後
更新するとSinatraのエラー画面が表示されるようになりました。

```
^C== Sinatra has ended his set (crowd applauds)
[2022-07-04 15:35:49] INFO  going to shutdown ...
[2022-07-04 15:35:49] INFO  WEBrick::HTTPServer#start done.
[2022-07-04 15:35:49] INFO  WEBrick 1.7.0
[2022-07-04 15:35:49] INFO  ruby 3.1.2 (2022-04-12) [arm64-darwin21]
== Sinatra (v2.2.0) has taken the stage on 4567 for development with backup from WEBrick
[2022-07-04 15:35:49] INFO  WEBrick::HTTPServer#start: pid=68679 port=4567
```

![スクリーンショット 2022-07-04 15.36.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/c60a8637-13a8-d5af-bd26-2fb77f8dc81d.png)

### Ctrl + Cを二度押した後
ここでやっと終了しました。

```
^C== Sinatra has ended his set (crowd applauds)
[2022-07-04 15:36:51] INFO  going to shutdown ...
[2022-07-04 15:36:51] INFO  WEBrick::HTTPServer#start done.
```
![スクリーンショット 2022-07-04 15.37.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246920/cce30a6e-8200-c7bc-af55-27d9860fa2e6.png)

## コード
最初はクラシックスタイルで開発し、途中からRubocopの[MixinUsage](https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/MixinUsage)の指摘を受けないようにするためモジュラースタイルに切り替えました。
モジュラースタイルに切り替えてから今回の問題が発生するようになりました。

### クラシックスタイル

``` app.rb
# frozen_string_literal: true

require 'sinatra'
require 'sinatra/reloader'

include ERB::Util

# 省略

get '/' do
  # 省略
end
```


### モジュラースタイル

``` app.rb
# frozen_string_literal: true

require 'sinatra'
require 'sinatra/reloader'

class App < Sinatra::Application
  include ERB::Util
  # 省略

  get '/' do
    # 省略
  end

  run! if app_file == $PROGRAM_NAME
end
```

# 解決策
[Sinatra requires CTRL+C twice to shutdown](https://github.com/sinatra/sinatra/issues/5) にて同様の現象が報告されていたため確認をしました。モジュラースタイルで書くときには `sinatra` ではなく `sinatra/base`をrequireするようです。

[Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ](http://sinatrarb.com/intro-ja.html#:~:text=Sinatra%3A%3ABase%20%2D%20%E3%83%9F%E3%83%89%E3%83%AB%E3%82%A6%E3%82%A7%E3%82%A2%E3%80%81%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%8A%E3%82%88%E3%81%B3%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%A9%E3%83%BC%E3%82%A2%E3%83%97%E3%83%AA)でも`sinatra/base`をrequireしています。しっかり読むべきでした……

ただし、今回は`require sinatra/base`を変更しただけでは終わりませんでした。

## require sinatra/baseに修正してみる
先ほどのapp.rbを修正します。

``` app.rb
# frozen_string_literal: true

require 'sinatra/base' # 修正
require 'sinatra/reloader'

class App < Sinatra::Application
  include ERB::Util
  # 省略

  get '/' do
    # 省略
  end

  run! if app_file == $PROGRAM_NAME
end
```

実行すると
```
$ bundle exec ruby app.rb 
```
何も起きなくなりました。

## sinatar/reloaderを消す
他に原因があるとするならばreloaderかもと思いコメントアウトしてみました。

``` app.rb
# frozen_string_literal: true

require 'sinatra/base' # 修正
# require 'sinatra/reloader'

class App < Sinatra::Application
  include ERB::Util
  # 省略

  get '/' do
    # 省略
  end

  run! if app_file == $PROGRAM_NAME
end
```
実行！
```
$ bundle exec ruby app.rb
[2022-07-05 11:55:35] INFO  WEBrick 1.7.0
[2022-07-05 11:55:35] INFO  ruby 3.1.2 (2022-04-12) [arm64-darwin21]
== Sinatra (v2.2.0) has taken the stage on 4567 for development with backup from WEBrick
[2022-07-05 11:55:35] INFO  WEBrick::HTTPServer#start: pid=4566 port=4567
```
動く！！
```
^C== Sinatra has ended his set (crowd applauds)
[2022-07-05 11:57:54] INFO  going to shutdown ...
[2022-07-05 11:57:54] INFO  WEBrick::HTTPServer#start done.
```
Ctrl+C一回で停止する！！！

# 理由はわからず…
> Sinatra::Base白紙の状態です。組み込みサーバーを含め、ほとんどのオプションはデフォルトで無効になっています。使用可能なオプションとその動作の詳細については、設定の構成を参照してください。アプリをトップレベルで定義する場合（クラシックスタイルとも呼ばれます）に似た動作が必要な場合は、次のようにサブクラス化できますSinatra::Application。
引用元： [README](http://sinatrarb.com/intro-ja.html)

## GitHubを確認してみる

### [Sinatra::Applicaton](https://github.com/sinatra/sinatra/blob/118b429d95145174260cd4c564e1bd5d05434e59/lib/sinatra/base.rb#L1903)

> Execution context for classic style (top-level) applications. All DSL methods executed on main are delegated to this class.
The Application class should not be subclassed, unless you want to inherit all settings, routes, handlers, and error pages from the top-level. Subclassing Sinatra::Base is highly recommended for modular applications.

Google先生に翻訳してもらいました。

>クラシックスタイル（トップレベル）アプリケーションの実行コンテキスト。 mainで実行されるすべてのDSLメソッドは、このクラスに委任されます。
トップレベルからすべての設定、ルート、ハンドラー、およびエラーページを継承する場合を除いて、Applicationクラスをサブクラス化しないでください。 Sinatra :: Baseのサブクラス化は、モジュラーアプリケーションに強くお勧めします。

[README](http://sinatrarb.com/intro-ja.html)で`Sinatra::Application`をサブクラス化する方法も紹介してるから、絶対にBaseを継承する必要はなさそう:thinking:

### [reloader](https://github.com/sinatra/sinatra/blob/master/sinatra-contrib/lib/sinatra/reloader.rb)

使用例のモジュラースタイルでは `Sinatra::Base`を継承してて、`Sinatra::Application`を継承する例は見つからず…。モジュラースタイルで`Sinatra::Application`を継承する場合は使わない方が良いのだろうか:thinking:

# 最後に
今回の事象は一人で解決できませんでした。現在参加してる[FjordBootCamp](https://bootcamp.fjord.jp/)で質問をして、メンターの[Shohei Umemoto](https://twitter.com/cafedomancer)さんに「GitHubのissueを確認してみよう！」とアドバイスをいただいた結果解決につながりました。
今回に限りませんが、自分のわからない！って状態を外に発信するのは非常に大事だなと感じます:smile: