web-dev-qa-db-ja.com

pdbを使用したプロセスの接続

pythonデッドロックがあると思われるスクリプトがあります。pdbでデバッグしようとしていましたが、ステップバイステップでデッドロックを取得できません。そして、返された出力によって、同じ繰り返しでハングしていないことがわかります。ロックされたときにのみスクリプトをデバッガーに添付したいのですが、可能であれば他のデバッガーを使用できます。

現時点では、pdbには、実行中のプログラムのデバッグを停止および開始する機能がありません。他にもいくつかのオプションがあります。

[〜#〜] gdb [〜#〜]

GDBを使用して、Cレベルでデバッグできます。実際のPythonスクリプトではなく、PythonのCソースコードをいじくり回しているので、これはもう少し抽象的ですが、場合によっては役立つことがあります。手順は次のとおりです。 https://wiki.python.org/moin/DebuggingWithGdb 。彼らはここに要約するにはあまりにも複雑です。

サードパーティの拡張機能とモジュール

「pdb接続プロセス」をグーグルで検索すると、PDBにこの機能を提供するいくつかのプロジェクトが明らかになります。
Pyringe: https://github.com/google/pyringe
Pycharm: https://blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/
Python wikiのこのページには、いくつかの選択肢があります。 https://wiki.python.org/moin/PythonDebuggingTools


特定のユースケースについて、回避策のアイデアがあります。

信号

UNIXを使用している場合は、 このブログ投稿 のように signals を使用して、実行中のスクリプトを停止してアタッチすることができます。

この引用ブロックは、リンクされたブログ投稿から直接コピーされます。

もちろん、pdbには、プログラムの途中でデバッガーを開始する機能、特にpdb.set_trace()が既にあります。ただし、これにはデバッグを開始する場所を知る必要があります。また、本番コード用にそのままにしておくこともできません。

しかし、私はGDBでできることを常にうらやましく思っていました。実行中のプログラムに割り込み、デバッガーで動き回るだけです。これは、状況によっては便利です。あなたはループで立ち往生していて、調査したいです。そして今日、それは突然私に起こりました:トレース機能を設定するシグナルハンドラを登録するだけです!ここで概念実証コード:

import os
import signal
import sys
import time    

def handle_pdb(sig, frame):
    import pdb
    pdb.Pdb().set_trace(frame)    

def loop():
    while True:
        x = 'foo'
        time.sleep(0.2)

if __name__ == '__main__':
    signal.signal(signal.SIGUSR1, handle_pdb)
    print(os.getpid())
    loop()

これで、実行中のアプリケーションにSIGUSR1を送信して、デバッガーを取得できます。ラブリー!

アプリケーションが端末に接続されなくなった場合に、Winpdbを使用してリモートデバッグできるようにすることで、この機能を強化できると思います。そして、上記のコードにある他の問題は、pdbを呼び出した後にプログラムを再開できないように見えることです。これはいくつかの方法で解決できます)。直近の最後の問題は、Windowsでこれを実行することです。Windowsについてはあまり知りませんが、Windowsには信号がないことを知っているので、そこでどのようにこれを実行できるかわかりません。

条件付きブレークポイントとループ

利用可能なシグナルがない場合、カウンタをインクリメントするループでロックまたはセマフォの取得をラップし、カウントが途方もなく大きな数に達した場合にのみ停止する場合、PDBを使用できる可能性があります。たとえば、デッドロックの一部であると思われるロックがあるとします。

lock.acquire() # some lock or semaphore from threading or multiprocessing

このように書き換えます:

count = 0
while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
    count += 1

    continue # now set a conditional breakpoint here in PDB that will only trigger when
             # count is a ridiculously large number:
             # pdb> <filename:linenumber>, count=9999999999

カウントが非常に大きくなったときにブレークポイントがトリガーされ、デッドロックが発生したことを(できれば)示します。ロックオブジェクトがデッドロックを示していないように見えるときにトリガーが発生する場合は、ループに短い時間遅延を挿入して、それほど速く増加しないようにする必要があります。また、適切なタイミングでブレークポイントをトリガーするには、ブレークポイントのトリガーしきい値をいじる必要があります。私の例の数は任意でした。

これに関する別のバリ​​エーションは、PDBを使用せず、ブレークポイントをトリガーする代わりに、カウンターが大きくなったときに意図的に例外を発生させることです。独自の例外クラスを作成する場合、それを使用して例外内のすべてのローカルセマフォ/ロック状態をまとめ、スクリプトの最上位でキャッチして終了する直前に印刷できます。

ファイルインジケータ

カウンターを正しく取得せずにデッドロックループを使用できる別の方法は、代わりにファイルに書き込むことです。

import time

while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
    with open('checkpoint_a.txt', 'a') as fo: # open a unique filename
        fo.write("\nHit") # write indicator to file
        time.sleep(3)     # pause for a moment so the file size doesn't explode

次に、プログラムを1〜2分間実行します。プログラムを強制終了し、それらの「チェックポイント」ファイルを調べます。デッドロックがストールしたプログラムの原因である場合、Wordの「ヒット」が大量に書き込まれているファイルは、どのロック取得がデッドロックの原因であるかを示します。

ループに定数だけでなく変数やその他の状態情報を出力させることで、この有用性を拡張できます。たとえば、ループ内でデッドロックが発生している疑いがあるが、それがどの反復で行われているのかわからないと言った。このロックループで、ループの制御変数またはその他の状態情報をダンプして、デッドロックが発生した反復を識別します。

44
skrrgwasme

pdb-clone と呼ばれる想像上のpdbのクローンがあります。これは 実行中のプロセスにアタッチ です。

メインプロセスのコードにfrom pdb_clone import pdbhandler; pdbhandler.register()を追加するだけで、pdb-attach --kill --pid PIDでpdbを起動できます。

11
eaglebrain