Flask の MySQL 接続と Life Cycle

# Flask の MySQL 接続と Life Cycle

mysql has gone away が現れるようになり、前から気になってたflask appのlife cycleについて理解しようとしてみた。
結局原因が途中でわかったので尻すぼみだけど、記録として残しておく。

## わかってないこと
- appはいつ起動していつ終わるのか？
    - urlに対する1 requestで app起動、コンテンツ返して終了、と思っていたが、gone awayってことはconnectionをkeepしている可能性がある
- MySQL接続はいつ切れるのか？
    - MySQL(app) ではない独自の接続を多数持っている。これらは切断されないのか？
    - 元のinstanceが廃棄されれば切断されると思うが、廃棄されているのか？


## きれいなflaskは毎回切断している
以下のコードで検証

```py
from flask import Flask
from flask_mysqldb import MySQL

app = Flask(__name__)


app.config['MYSQL_USER'] = 'report_local'
app.config['MYSQL_PASSWORD'] = 'report_local'
app.config['MYSQL_HOST'] = 'sys-test-v.dev.com'
app.config['MYSQL_DB'] = 'mysql'
app.config['MYSQL_PORT'] = 20306
app.config['MYSQL_CURSORCLASS'] = 'DictCursor'

mysql = MySQL(app)

@app.route('/')
def users():
    cur = mysql.connection.cursor()
    cur.execute('''SELECT CONNECTION_ID(); ''')  # mysql connection idを取得
    rv = cur.fetchall()
    return "app obj id = %s, %s" % (id(app), str(rv))   # <----- appのobject idも返す

if __name__ == '__main__':
    app.run(debug=True)
```		
> 参考 https://github.com/admiralobvious/flask-mysqldb

### 結果
- appは作り直されない
- MySQLは切断されていることがわかった
- 

```py
# first time
app obj id = 4424594128, ({'CONNECTION_ID()': 7196},)

# second time
app obj id = 4424594128, ({'CONNECTION_ID()': 7197},)

# and so on...
app obj id = 4424594128, ({'CONNECTION_ID()': 7198},)
```

appが作り直されないのは、app自体はあくまで初回の python run.py をした時点 = Web Serverを起動した時点で同時に生成されるから・・なんだろうか。


# 自分のいけてない MySQLdb で検証

show processlist; を見ていたら、やっぱり自分のflaskは切断してなかった。大量にコネクションが残ってた。


# webサーバとアプリサーバが完全にわからない

appが作り直されない理由がよくわからない。

これは、根本的にwebサーバを理解していないのが問題なんだと思う。
webサーバは、起動するとソケット（ポート）をlistenにして接続を待ち受ける。
リクエストが来たら、定められた位置にあるファイルを取って渡すだけだ。
そこにあるのがアプリなら、アプリを起動する・・・・そう思っていけれど。

web serverとpython applicationの間は、どうなっているんだろうか？
web server processを造ったとき、同時に受けてであるアプリも作られる。

アプリ側も待機しないといけない。socketかportで、web-serverからのリクエストに答えるために
ずっと起動してlistenし続けている。これがアプリサーバか。
pythonのhttp serverだと、全部ひとつで完結していて、その結果アプリサーバが起動する・・・
当然、起動したときのものはずっと残る・・・
single threadの場合は、そのアプリが永遠に行き続ける・・のだろうか。
multi threadだったら？ アプリサーバは、あくまでメインスレッドが起動するだけ？
リクエストに応じてappが作られる？むー・・それだとつじつまが合わない気がする


# simple な web serverで検証

たぶんflaskのdev serverはこれを使ってるんじゃなかろうか。

```py
import http.server
import datetime


class App():
   def show(self):
       return "Hello %s" %  datetime.datetime.now()

app = App()

class myHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type','text/html')
        self.end_headers()
        self.wfile.write(str.encode("app id=%s, %s" % (str(id(app)), app.show())))
        return

server_address = ("", 8000)
simple_server = http.server.HTTPServer(server_address, myHandler    )
simple_server.serve_forever()
```
> 参考 https://docs.python.org/3/library/http.server.html#http.server.HTTPServer




# で、結局原因はclass変数だった

> appが作り直されない理由がよくわからない。

これを書いて数週間経った今の理解で行くと、appインスタンスは作り直される。**インスタンス**は作り直されるが、app **クラス** は最初に作ったときのままだ。
pythonではクラス変数は最初の起動から永遠に再利用される。
今回の原因は、appから呼び出している自作クラスのmysql connection poolをいれる変数がclass変数だった。

```py
class MyClass:
    dbpool = {}        # ←これが原因

    def __init__(self):
        self.dbpool = {}     # ←インスタンス変数にして解決した
```

class変数はアプリが最初に起動された状態を永遠に保持するので、コンテンツを返した後も残り続ける。
自分のclassは、self.dbpoolの中にconnectionオブジェクトが残っている限り再利用する構造にしていたので、
オブジェクトは残るが、オブジェクト自体は数時間で接続が切れている、という状態だった。
切れている接続を再利用すると mysql has gone away になる。

いま考えれば con.close() する時に self.dbpool の中を空にしていなかったのかもしれない。

いずれにしても、インスタンス変数にした結果、アクセスのたびに self.dbpool が初期化されるようになったので
古いconnectionオブジェクトを再利用することはなくなった。そういえばself.dbpoolを空にする修正も一応した気がしてきた。

個人的には ↓ に近いような問題だと思っていて、いや普通なのかもしれないけど、pythonなんだかなぁと感じたりしている。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Pythonのこれ、流石に言語としてどうなのという気持ちになるんだが… <a href="https://t.co/degIU4tPeI">pic.twitter.com/degIU4tPeI</a></p>&mdash; 幸福のデータ科学㈱教祖テラモナギ (@teramonagi) <a href="https://twitter.com/teramonagi/status/1263464328826621952?ref_src=twsrc%5Etfw">May 21, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



おしまい