彷徨えるフジワラ

年がら年中さまよってます

virtualenvwrapper.el での環境変数設定を修正

Emacs でのソースコード編集時に Language Server Protocol 経由での補完候補列挙やエラー/警告表示を行うために、 以下のツール群をインストールしてみました。

  • pyls: Language Server Protocol で Jedi と連携するためのフロントエンド
  • Jedi: Python での補完候補列挙やエラー/警告表示を行うツール/pyls のバックエンド
  • lsp-mode: Language Server と連携するための Emacs パッケージ

上記構成では、Emacs 経由で起動された pyls/Jedi プロセスと lsp-mode を介して Language Server Protocol で連携することになります。

素の環境で開発する分にはこれでも良いのですが、 プロジェクトや作業毎に個別の virtualenv 環境を使用するようなケースでは、 以下のいずれかの対応が必要になります。

  • 対象 virtualenv を activate した上で Emacs を起動
  • Emacs 上で VIRTUAL_ENV 環境変数 (+ 相応の PATH) を設定した上で pyls/Jedi プロセスを起動

前者の対応方式は最大で以下の3手順を必要とするため、利便性が大幅に削がれますし、アプリケーションランチャーの使用が制限されてしまいます。

  1. terminal を起動
  2. terminal 上で virtualenv を activate
  3. emacs を起動

そこで、Emacsvirtualenvwrapper.el を導入することで、後者の対応方式を採用することにしました。

しかし virtualenvwrapper.el を併用した場合、 一定時間以上使い続けていると OSError(24, ’Too many open files’) によって pyls/Jedi プロセスが終了してしまうケースが多々発生します (POSIX のエラーシンボル的には EMFILE)。

調べてみると以下のような経緯で pyls/Jedi が終了していました。

  1. Emacs から lsp-mode 経由で Language Server Protocol による要求の発行
  2. Jedi は起動済み外部 Python プロセスの sys.prefixVIRTUAL_ENV を比較
  3. 一致している場合は起動済み外部 Python プロセスを再利用
  4. 不一致の場合は外部 Python プロセスを新規作成
  5. 既存の外部プロセス連携のための FIFO (pipe) が閉じられないまま放置
  6. FIFO 放置により同時 open 可能ファイル数を消費
  7. Language Server Protocol による要求発行の都度、上記手順が繰り返し実施
  8. エンドユーザプロセスの同時 open 可能ファイル数上限超過
  9. pyls/Jedi で新規の外部プロセスの生成ができなくなる (= EMFILE)
  10. 要求に応答できないため pyls/Jedi が終了

Python プロセスの sys.prefixVIRTUAL_ENV の不一致の要因は、 通常の activate と virtualenvwrapper.el での設定値の違いに起因しています。

  • Python プロセスの sys.prefix は virtualenv 有効化の有無に関わりなく末尾に "/" を 含まない
  • virtualenv の通常有効時の VIRTUAL_ENV は末尾 "/" を 含まない
  • virtualenvwrapper.el が VIRTUAL_ENV に設定する値は末尾に "/" が 含まれる

外部 Python プロセスとの連携用 FIFO を閉じずに放置する Jedi の実装は如何なものかと思いますが (笑)、 プロセス連携の際の環境変数値のフォーマットは API 仕様的な側面がありますから、 virtualenvwrapper.el 側でも修正を実施するのが妥当だと思われます。

そこで VIRTUAL_ENV 末尾に "/" を含めないようにする修正を作成しました。

github.com