パスワードを忘れた? アカウント作成
13602304 journal
日記

tuneoの日記: Pythonのcontextlibでハマる 5

日記 by tuneo

ロックファイルを使ったスクリプトの排他制御をやろうと思ってcontextlibに手を出してみた。

つまり

with lockfile("/var/lock/test.lock"):
  排他的な何か
 
排他的じゃない何か

みたいな感じで書けると便利だな、と思ったわけである。
お気楽にこの手の処理を実現するためにcontextlibモジュールというモノがあり、そしてそこにはジェネレータでコンテキストマネージャをお気楽に実装するためのデコレータがcontextlib.contextmanagerがある、というところまでは理解した。

import contextmanager from contextlib as cm
import os
import errno
 
class Locked(Exception):
  pass
 
@cm
def lockfile(filename):
  try:
    fd = os.open(filename, os.O_CREAT | os.O_EXCL)
    yield fd
  except OSError as e:
    if e.errno == errno.EEXIST:
      raise Locked
    else:
      raise
  else:
    os.close(fd)
    os.remove(filename)

これでイケると思ったんだがいまいち芳しくない。

withブロックの中でsys.exit()とかraise SystemExitしたりKeyboardInterrupt例外が発生すると、コンテキストマネージャお構いなしでプロセスが終了するのか?だとするとあんまりご利益無いなー。

この議論は、tuneo (2938)によって テキとトモのテキ禁止として作成されたが、今となっては 新たにコメントを付けることはできません。
  • 異常終了時にも後始末をするためには

    import atexit
     
    ...
     
    def cleanup(filename, fd):
      os.close(fd)
      os.remove(filename)
     
    ...
     
    try:
      fd = os.open(...)
      atexit.register(cleanup, (filename, fd))
      ..

    とか書かないとダメ?

    • by ktmizugaki (46208) on 2018年05月21日 11時17分 (#3411907) 日記

      try ... finally を使えばいいのでは?
      http://blog.amedama.jp/entry/2015/10/02/234946 [amedama.jp] とか https://qiita.com/QUANON/items/c5868b6c65f8062f5876 [qiita.com] とか見ると、そうしています。

      --
      svn-init() {
        svnadmin create .svnrepo
        svn checkout file://$PWD/.svnrepo .
      }
      親コメント
      • ここがちょいと面倒なところなのですが、当然のことながら「別プロセスが作成したロックファイルは触らずにLocked例外をraiseしたい」わけですよ。

        だもんでfinallyにos.removeを書くとあまり都合がよくないのです。

        逃げる手は思い付かないではないですが、is not NoneとかPythonicじゃないのが好かないんですよね。

        @cm
        def lockfile(filename):
          fd = None
          try:
            fd = os.open(filename, os.O_CREAT | os.O_EXCL)
            yield fd
          except OSError as e:
            if e.errno == errno.EEXIST:
              raise Locked
            else:
              raise
          finally:
            if fd is not None:
              os.close(fd)
              os.remove(filename)

        親コメント
        • by ktmizugaki (46208) on 2018年05月22日 11時54分 (#3412509) 日記

          別途 try するのは?

          @cm
          def lockfile(filename):
              fd = None
              try:
                  fd = os.open(filename, os.O_CREAT | os.O_EXCL)
              except OSError as e:
                  if e.errno == errno.EEXIST:
                      raise Locked
                  else:
                      raise
              try:
                  yield fd
              finally:
                  os.close(fd)
                  os.remove(filename)

          --
          svn-init() {
            svnadmin create .svnrepo
            svn checkout file://$PWD/.svnrepo .
          }
          親コメント
typodupeerror

海軍に入るくらいなら海賊になった方がいい -- Steven Paul Jobs

読み込み中...