skydum

個人的な作業記録とか備忘録代わりのメモ

Flaskでファイルを使った排他処理

Flaskでファイルを使った排他処理

どうしてもFlask(マルチプロセス)で動作しているプログラムで排他処理をしたくなった。(必要に迫られた)

Linuxなら以下を利用すると良いと思うけれども、Windows環境なので利用できず。

Windowsでファイルを使った履いた処理ってどうするのかな?と思って調べたので、忘れないように記載しておく。

msvcrt

Windowsの場合は以下のモジュールを利用すれば良いらしい

msvcrt.locking

以下のmsvcrt.lockingを使うことによってファイルのロックの取得とリリースを行うことができる。
オプションがいくつかあって使わないと忘れてしまいそう。

msvcrt.locking(fd, mode, nbytes)

使い方

事前に排他処理を行うために利用するファイルを作成しておく。 その上で

LOCKFILE = open("lockfile.lock", "bw")

msvcrt.locking(LOCKFILE.fileno(), msvcrt.LK_NBLCK, 1)

の様にするとファイルのロックを取得できる。

第1引数

ファイルディスクリプタ(fd):ファイルの番号を指定する

第2引数に指定できるオプション

読み込み、書き込みともにロックを掛ける
mode 機能説明
msvcrt.LK_LOCK 指定されたnbytesの範囲にロックを掛ける
ロックが取得できるまで1秒間隔で10回リトライを行う
10回リトライを行ってもロックが取得できない場合OSErrorが返る
msvcrt.LK_NBLCK 指定されたnbytesの範囲にロックを掛ける
ロックが取得できない場合即時にOSErrorが返る
ファイルの読み込みはできるが、書き込みができない様にロックを掛ける

今回は利用しないので検証していません

mode 機能説明
msvcrt.LK_NBRLCK 指定されたnbytesの範囲にロックを掛ける
ロックが取得できるまで1秒間隔で10回リトライを行う
10回リトライを行ってもロックが取得できない場合OSErrorが返る
msvcrt.LK_RLCK 指定されたnbytesの範囲にロックを掛ける | ロックを掛けた範囲を読み込むことはできるが書き込むことはできない

第3引数

ファイルにロックを書ける範囲をバイト数で指定する

サンプルコード

以下のコードでは、2つのエンドポイント(/lockと/unlock)を作成し、ファイルのロックを取得、開放するサンプルです

  • /lock:ファイルのロックを取得する
  • /unlock:ファイルのロックを開放する

機能

  1. /lockにアクセスするとファイルのロックを取得することができ、再度/lockにアクセスするとロックに失敗することが確認できます
  2. /unlockにアクセスした後/lockにアクセスすると/lockが再度取得できることが確認できます
  3. /lockを取得した後、再度/lockを取得すると失敗しますが、ロックの取得は内部的に1秒間隔で10回挑戦を行いますので、挑戦を行っている間に/unlockにアクセスすると問題なくロックが取得できることが確認できます
import msvcrt
import time

from flask import Flask, jsonify

app = Flask(__name__)

# ロックファイルを開く
LOCKFILE = open("lockfile.lock", "bw")

# ロックリトライの最大回数とインターバル
LOCK_RETRY_MAX = 10
LOCK_INTERVAL = 1

# ロックを取得するためのデコレータ
def retry(func):
    def wrapper(*args, **kwargs):
        for _ in range(LOCK_RETRY_MAX):
            res = func(*args, **kwargs)

            if res is True:
                return res

            # ロックが取得できなかった場合、インターバルを待って再試行する
            time.sleep(LOCK_INTERVAL)

        return False

    return wrapper

# ロックを取得する関数
@retry
def acquire_lock() -> bool:
    try:
        msvcrt.locking(LOCKFILE.fileno(), msvcrt.LK_NBLCK, 1)  # ノンブロッキングロックを取得
        return True
    except OSError:
        return False

# ロックを解放する関数
def release_lock() -> bool:
    try:
        msvcrt.locking(LOCKFILE.fileno(), msvcrt.LK_UNLCK, 1)  # ロックを解放
        return True
    except OSError:
        return False

# ロックを取得するエンドポイント
@app.route("/lock")
def acquire_lock_test():
    res = acquire_lock()

    if res is True:
        return jsonify({"status": "locked"})
    else:
        return jsonify({"status": "lock failed"})

# ロックを解放するエンドポイント
@app.route("/unlock")
def release_lock_test():
    res = release_lock()

    if res is True:
        return jsonify({"status": "unlocked"})
    else:
        return jsonify({"status": "unlock failed"})

# アプリケーションの実行
if __name__ == "__main__":
    app.run()