行儀の悪い Python ライブラリ
某実験施設では測定データを取り出すのに独自の Python パッケージ(C ライブラリの wrapper)を利用する。このパッケージのソースコードは非公開である。仕様書には multithread 非対応と書いてあるが、スレッド並列化のみならず fork によるプロセス並列化でも失敗する。パッケージが提供するメソッドを呼ぶタイミングが同時にならないようにコードを書いてもダメで、親プロセスで一度でもメソッドを呼んでしまうと、関係するオブジェクトをすべて破棄したあとで fork したとしても、子プロセスからメソッドを呼んだ時に不規則にクラッシュする。Spawn なら大丈夫だが、コードが複雑になるし立ち上げが遅いので嬉しくない。パッケージが行うタスクの性質上 fork できない理由がなさそうなのだが、開発元に問い合わせても「非対応」の一点張りで詳細を教えてくれない。
何らかのリソースリークが発生している懸念さえあるので、strace
と lsof -i
にて挙動を解析した。このパッケージは実験メタデータを取得するために施設内の MySQL サーバと通信するのだが、利用が終わってオブジェクトをすべて開放したあとでも、接続が開放されないことが分かった。この socket が fork によって複製されると、複数の子プロセスから同一のコネクションを通じて一気にサーバに話しかけるので問題が生じるのだ。
そこで、psutil.Process().connections
で当該コネクションの file descriptor を取得し、fork 前に強引に os.close()
してみた。Fork 先では無効な socket にアクセスしようとして死ぬかと思いきや、なんと適切にソケットを開き直すことが分かった。パッケージ内のグローバル変数として socket をキャッシュしており、それが閉じていれば/失敗すれば開き直すという実装になっているのかなと想像するが、パッケージのコードが非公開なのでこれ以上は分からない。Undocumented な挙動なので production code に使うこともできないが、興味深い体験だったし、psutil
という便利なパッケージのことを知れたのでよしとする。
Qt アプリの HiDPI 対応
Linux で Spotlight 的な PDF 検索を実現するため、recoll
を入れてみた。その GUI のフォントが小さすぎるので、この記述 に従って、
export QT_AUTO_SCREEN_SCALE_FACTOR=1
したら良い感じになった。