process-executor

process-executor は、通常の Python クラスのメソッド呼び出しを、multiprocessing の worker process 上で job として実行するための execution layer です。

この Pages 本文は、今回与えられた executor.py と shared_context.py の実装だけを根拠として新規に記述しています。既存 README / CHANGELOG の文章は根拠として使用していません。

このライブラリの中心は、既存クラスを multiprocessing 専用クラスへ作り替えることではありません。worker process の中に通常のクラス instance を一つ作り、Queue から届いた handler 名を getattr() で method に解決し、その method を job として実行します。

caller / application
        |
        | submit("move", ...)
        v
+-------------------------+
| ProcessExecutor         |
|                         |
| jobs / workers / state  |
| multiprocessing.Queue   |
+------------+------------+
             |
       +-----+-----+
       |           |
       v           v
   Worker A     Worker B
       |           |
       v           v
  Ctrl instance  Ctrl instance
       |           |
       v           v
   handler(...) handler(...)

つまり責務は次のように分離されます。

既存クラス
    └─ 実際の処理、装置制御、計算、protocol、domain logic

ProcessExecutor
    └─ process、Queue、job ID、状態、結果、例外、worker lifecycle

SharedContext
    └─ process 間で明示的に共有する metadata / state / lock

Quick Start

既存クラスをそのまま worker にする

from process_executor import ProcessExecutor


class Calculator:
    def add(self, a: int, b: int) -> int:
        return a + b


def main() -> None:
    with ProcessExecutor(
        worker_class=Calculator,
        worker_count=2,
        start_method="spawn",
    ) as executor:
        job_id = executor.submit("add", 10, 20)
        result = executor.wait(job_id, timeout_sec=5.0)
        print(result)


if __name__ == "__main__":
    main()

Calculator には multiprocessing.Process も Queue もありません。

worker process の中では概念的に次の処理になります。

instance = Calculator()

job = job_queue.get()
handler = getattr(instance, job["handler"])
result = handler(*job["args"], **job["kwargs"])

一つの worker-local instance は、一つの job だけのために作られるのではありません。worker process の起動時に一度作られ、同じ worker が取得した複数の job で再利用されます。


1. ProcessExecutor が抽象化しているもの

Python のクラスを multiprocessing で実行しようとすると、処理本体とは別に多くの infrastructure が必要になります。

たとえば、処理本体が次だけだったとします。

class Controller:
    def move(self, position):
        ...

multiprocessing 化を Controller 自身へ直接書き込むと、Controller が次まで持つことになります。

Controller
├─ move()
├─ Process の生成
├─ Queue の生成
├─ worker loop
├─ job ID
├─ job state
├─ result
├─ exception transport
├─ worker state
├─ worker restart
└─ shutdown

これは move() の意味と process management の意味を一つのクラスへ混在させます。

ProcessExecutor は後半を generic execution infrastructure として切り出します。

Controller
└─ move()

ProcessExecutor
├─ Process
├─ Queue
├─ Manager
├─ jobs
├─ workers
├─ state
├─ lock
├─ submit
├─ result
├─ wait
└─ close

この分離によって、既存クラスを multiprocessing なしでも使える状態に保ちながら、必要な場所だけ上位から multi-process execution を組み合わせられます。


2. constructor の全体像

ProcessExecutor.__init__() は、worker の作り方を確定した後、multiprocessing infrastructure を構築します。

実装上の順序は次です。

1. worker_count を検証
2. worker_factory / worker_class の指定を検証
3. worker factory を確定
4. multiprocessing context を取得
5. Manager を生成
6. jobs dictionary を生成
7. workers dictionary を生成
8. state dictionary を生成
9. RLock を生成
10. Queue を生成
11. 親 process 側の Process table を生成
12. closed=False
13. worker_count 個まで worker を起動

multiprocessing context は次で取得されます。

multiprocessing.get_context(start_method)

したがって Manager、Queue、Process は同じ multiprocessing context から作られます。

start_method=None なら Python がその platform で選ぶ default が使われます。ProcessExecutor 自身が platform ごとに start method を強制するコードはありません。


3. worker_count

worker_count は 1 以上でなければなりません。

if worker_count < 1:
    raise ValueError("worker_count must be 1 or greater")

設定値は executor の lifetime 中に維持すべき worker process 数として使われます。

constructor は最初にその数まで worker を起動します。

その後も dead worker を検出した場合、同じ worker_count に戻るまで replacement worker を起動します。

現在の source には、生成後に worker_count を変更する public API はありません。

したがってこれは dynamic autoscaling pool ではなく、constructor で決めた固定 target size を維持する worker pool です。


4. worker_class と worker_factory

worker-local instance の生成方法は二つあります。

worker_class

既存クラスをそのまま使う場合です。

executor = ProcessExecutor(
    worker_class=Controller,
    worker_args=("COM3",),
    worker_kwargs={"timeout": 1.0},
)

内部では _WorkerClassFactory が作られます。

この factory は worker process 内で呼ばれ、次を実行します。

Controller(
    *worker_args,
    **worker_kwargs,
)

SharedContext は _WorkerClassFactory.__call__() に渡されますが、既存 controller class には渡しません。

このため既存クラスは ProcessExecutor 用の constructor signature を持つ必要がありません。

worker_factory

worker-local instance の生成に SharedContext が必要な場合です。

def create_controller(context):
    return Controller(
        shared_state=context.state,
        lock=context.lock,
    )


executor = ProcessExecutor(
    worker_factory=create_controller,
)

factory は worker process の中で:

instance = worker_factory(context)

として呼ばれます。

worker_factory と worker_class は同時には指定できません。

worker_factory を使う場合、worker_args と worker_kwargs も指定できません。

この区別によって、

既存クラスを無変更で使う
        -> worker_class

process-shared dependency を注入して生成する
        -> worker_factory

という二つの利用形を分けています。


5. worker process が保持する instance

worker main の最初で SharedContext を作ります。

context = SharedContext(
    jobs=jobs,
    workers=workers,
    state=state,
    lock=lock,
)

次に:

instance = worker_factory(context)

を一度だけ実行します。

その後に while True の job loop に入ります。

したがって worker-local instance の寿命は:

worker process start
        |
        v
instance creation
        |
        v
job 1
        |
        v
job 2
        |
        v
job 3
        |
        v
STOP
        |
        v
worker process end

です。

job ごとに instance を作り直す構造ではありません。

これは stateful な controller を worker 内で保持できることを意味します。

ただし worker A の instance と worker B の instance は別 process にあります。

Worker A                     Worker B
---------                    ---------
Controller A                 Controller B
self.x = 10                  self.x = 20

通常の instance attribute は共有されません。


6. SharedContext

SharedContext は非常に小さいクラスです。

保持するものは四つだけです。

jobs
workers
state
lock

constructor は渡された object をそのまま instance attribute に保存します。

SharedContext 自体が IPC を実装するのではありません。

実際に process 間共有を成立させているのは、ProcessExecutor が Manager から生成した proxy object です。

SharedContext
    |
    +-- jobs    -> Manager.dict()
    +-- workers -> Manager.dict()
    +-- state   -> Manager.dict()
    `-- lock    -> Manager.RLock()

SharedContext はこれらを一つに束ね、worker factory へ渡す interface です。


7. jobs と workers と state は同じ dictionary ではない

三つとも Manager dictionary ですが、役割は異なります。

jobs は ProcessExecutor が job execution metadata を管理するために使います。

workers は ProcessExecutor が worker execution metadata を管理するために使います。

state は executor core が schema を決めない shared mapping です。

jobs
    executor が意味を定義

workers
    executor が意味を定義

state
    controller / application が意味を定義

この分離は重要です。

controller-specific state を jobs metadata の中へ混ぜる必要はありません。

逆に ProcessExecutor の worker state を application の generic state key として管理する必要もありません。


8. lock

lock は Manager の RLock です。

self._lock = self._manager.RLock()

この lock は worker へ SharedContext 経由で渡されます。

しかし ProcessExecutor は handler 実行前後で自動的に lock を取りません。

つまり:

handler = getattr(instance, handler_name)
result = handler(*args, **kwargs)

の外側に generic lock はありません。

そのため worker_count > 1 なら複数 worker の handler は同時に実行され得ます。

shared state や単一 resource に対して排他が必要なら、controller 側が SharedContext.lock を使う設計にできます。

この形にしているため、すべての handler を無条件に serialise することなく、必要な critical section だけを利用側で定義できます。


9. submit

submit() は handler call を直接実行しません。

最初に closed flag を確認します。

closed なら:

RuntimeError("ProcessExecutor is already closed")

です。

handler は文字列でなければなりません。

文字列でなければ TypeError、空文字なら ValueError です。

次に _replenish_workers() を呼びます。

その後 UUID4 hex から job ID を生成します。

job_id = uuid.uuid4().hex

job metadata を queued で作ります。

job_id
handler
args
kwargs
state = queued
worker_id = None
result = None
error = None
created_at
started_at = None
finished_at = None

そして Queue に execution payload を入れます。

Queue payload は metadata 全体ではなく:

job_id
handler
args
kwargs

です。

最後に job ID を返します。

したがって submit() の return は handler result ではありません。


10. job ID の意味

job ID は一回の submit を識別する UUID4 hex です。

同じ handler を同じ arguments で二回 submit しても job ID は別になります。

submit("move", 100) -> job A
submit("move", 100) -> job B

job ID を使って:

executor.state(job_id)
executor.job(job_id)
executor.result(job_id)
executor.wait(job_id)

を呼びます。

job ID は worker ID とは別です。

job は worker に割り当てられる前に作られるため、queued の時点では worker_id=None です。


11. queued

queued は submit が metadata を作成した直後の state です。

この段階では:

worker_id = None
started_at = None
finished_at = None
result = None
error = None

です。

Queue には job payload が入っていますが、どの worker が取得するかはまだ決まっていません。

ProcessExecutor には caller が worker ID を指定して submit する API はありません。

したがって job routing は generic shared Queue に任されています。


12. worker startup

_start_worker() は新しい worker ID を UUID4 hex で作ります。

次に multiprocessing Process を生成します。

target は _worker_main です。

渡されるものは:

worker_id
worker_factory
job_queue
jobs
workers
state
lock

です。

その Process を daemon にします。

process.daemon = True

そして start() します。

最後に親 process の _processes dictionary へ:

worker_id -> Process object

として保存します。


13. 親側 _processes と共有 workers の違い

_processes は通常の Python dictionary です。

これは parent process が Process object を保持するために使います。

_processes
worker_id -> multiprocessing.Process

一方 workers は Manager dictionary です。

こちらには:

worker_id
pid
state
current_job_id
current_handler

が入ります。

つまり:

_processes
    process lifecycle を操作するため

workers
    worker の状態を観測するため

です。

この二つを同一視しないことが内部構造を理解する上で重要です。


14. worker readiness

worker metadata は _start_worker() の親 process 側では登録されません。

worker process が実際に開始し、SharedContext を作り、worker factory で instance を生成した後に:

workers[worker_id] = {
    ...
    "state": "idle",
}

と登録されます。

したがって:

Process.start()
        |
        | 時間差があり得る
        v
factory(context)
        |
        v
workers[worker_id] = idle

です。

constructor は全 worker が idle になるまで待つ barrier を持ちません。

よって constructor が return した瞬間に、全 worker-local controller の initialization が完了しているとまでは source から保証できません。


15. idle

worker-local instance の生成が完了すると worker metadata は idle になります。

worker_id
pid
state = idle
current_job_id = None
current_handler = None

idle worker は Queue.get() で次の job を待っています。

Queue は全 worker に共通です。

したがって複数 idle worker があれば、Queue から job を取得した worker がその job を実行します。

特定 worker への affinity はありません。


16. worker が job を取る

worker は:

job = job_queue.get()

で block します。

通常 job を受け取ると payload から:

job_id
handler
args
kwargs

を取り出します。

次に worker metadata を running に更新します。

state = running
current_job_id = job_id
current_handler = handler_name

PID と worker ID も metadata に保持されます。

この時点で「どの worker が、どの job の、どの handler を実行しているか」が観測できます。


17. job を running にする

worker metadata の更新に続いて、jobs dictionary から対象 job metadata の snapshot を取ります。

そして:

state = running
worker_id = current worker
started_at = current UNIX time

へ更新します。

このため queued から running への transition は worker が job を実際に取得した時点で起きます。

submit 時点では started_at は設定されません。

created_at と started_at の差は、job が Queue 上で待った時間を含みます。


18. handler dispatch

実行対象は:

handler = getattr(instance, handler_name)

で取得します。

その後:

result = handler(*args, **kwargs)

です。

ProcessExecutor は handler ごとの signature を事前に登録しません。

handler name に対応する attribute が存在するか、arguments が signature と一致するかは実行時に Python が判定します。

存在しない handler 名なら getattr() が例外になります。

arguments が不正なら method call が例外になります。

どちらも worker の except Exception に入るため job failure として記録されます。


19. handler 名と安全境界

submit() が検証する handler は:

str であること
空文字でないこと

だけです。

_ で始まる method を禁止する code や allowlist はありません。

つまり source の dispatch mechanism 自体は:

getattr(instance, handler_name)

で取得できる attribute を対象にします。

外部 HTTP request 等から handler 名をそのまま受け取る場合、公開可能 handler を制限する responsibility は上位 layer にあります。

ProcessExecutor 自体を remote procedure call security boundary と考えるべきではありません。


20. succeeded

handler が正常 return すると result を job metadata に保存します。

state = succeeded
result = handler return value
error = None
finished_at = current UNIX time

ここで重要なのは succeeded の意味です。

ProcessExecutor が確認しているのは Python handler call が exception を送出せず return したことです。

例えば handler が:

return False

と返しても exception ではありません。

その場合 executor-level job state は succeeded です。

したがって physical device success や domain validation は handler/controller の責務です。


21. failed

handler execution が Exception を送出すると job state は failed になります。

保存する error は:

type
message
traceback

です。

type は:

exc.__class__.__name__

message は:

str(exc)

traceback は:

traceback.format_exc()

です。

result は None に設定されます。

finished_at も設定されます。


22. handler failure 後の worker

handler が failed になっても worker process は終了しません。

exception handling の後で worker metadata は:

state = idle
current_job_id = None
current_handler = None

に戻ります。

したがって:

job failure
    !=
worker failure

です。

通常の Python Exception は一つの job の失敗として隔離され、同じ worker-local instance は次の job を処理できます。

ただし handler が instance state を途中まで変更してから exception になった場合、その instance state を自動 rollback する機能はありません。


23. state(job_id)

state() は内部で job(job_id) を呼びます。

job が存在しなければ None です。

存在すれば metadata の "state" を返します。

したがって unknown job ID は exception ではなく None です。

これは result() / wait() の unknown job ID が KeyError になる挙動とは異なります。


24. job(job_id)

job() は shared jobs dictionary から metadata を取得します。

存在しなければ None。

存在すれば:

dict(metadata)

として通常の dictionary に変換して返します。

Manager proxy をそのまま公開していません。

返された dictionary は snapshot です。

後から worker が shared metadata を更新しても、すでに取得した local dictionary が自動更新されるわけではありません。


25. jobs()

jobs() は全 jobs を走査し、各 metadata を通常の dictionary に変換します。

返り値は:

job_id -> metadata snapshot

の dictionary です。

completed job を削除する code は source にありません。

したがって executor の lifetime 中に submit された job metadata は jobs dictionary に蓄積します。

大量の job を長時間投入する運用では、この retention は考慮対象になります。


26. worker(worker_id)

worker() は metadata を読む前に _replenish_workers() を呼びます。

したがって純粋な read-only operation ではありません。

dead worker が存在すれば、この呼び出しを契機に replacement worker が起動する可能性があります。

指定 worker ID が workers dictionary に存在しなければ None。

存在すれば metadata snapshot を返します。


27. workers()

workers() も最初に _replenish_workers() を呼びます。

その後、workers dictionary の全 metadata を snapshot にして返します。

dead worker metadata は replenish 時に dead に更新されますが、workers dictionary から削除されません。

replacement worker は新しい UUID の worker ID で追加されます。

そのため workers() は現在 alive な worker だけではなく、過去に dead になった worker metadata を含む可能性があります。


28. result(job_id)

result() は completed result の読み出しです。

wait ではありません。

unknown job ID:

KeyError(job_id)

failed:

RuntimeError(str(error_metadata))

succeeded 以外:

RuntimeError("job is not completed")

succeeded:

metadata["result"]

です。

元 worker で発生した exception class を parent process で同じ class として再送出する仕組みではありません。


29. wait(job_id)

wait() は polling loop です。

開始時刻は time.monotonic() で取ります。

loop 内で job(job_id) を繰り返し呼びます。

succeeded なら result。

failed なら RuntimeError。

timeout を超えたら TimeoutError。

それ以外なら:

time.sleep(interval_sec)

して再確認します。

Condition や Event を使った通知型 wait ではありません。


30. timeout は execution cancellation ではない

wait(timeout_sec=...) が timeout しても Queue から job を削除しません。

running handler を停止しません。

job state を failed に変更しません。

したがって timeout の意味は:

caller がこの wait を続けない

です。

execution deadline ではありません。

timeout 後も worker が job を実行し続け、後から succeeded になる可能性があります。


31. cancellation

source には cancel(job_id) がありません。

queued job を Queue から選択的に取り除く API もありません。

running handler に cancellation token を渡す仕組みもありません。

job 単位で worker process を terminate する API もありません。

したがって cancellation は現在の ProcessExecutor の機能ではありません。


32. priority

job queue は通常の multiprocessing Queue です。

priority field は job payload にありません。

priority queue でもありません。

したがって source には high/normal/low priority scheduling はありません。


33. ordering

submit は Queue.put を順に行います。

しかし worker_count が複数なら job は並行実行されます。

そのため completion order は submit order と一致する保証がありません。

submit A
submit B

Worker 1: A ----長い----> complete
Worker 2: B -> complete

のように B が先に完了できます。

ProcessExecutor は completion order を submit order に並べ直す gather API を持ちません。


34. job metadata の timestamp

job には三つの timestamp があります。

created_at は submit 時。

started_at は worker が job を running にした時。

finished_at は succeeded/failed にした時。

したがって:

queue wait
    = started_at - created_at

handler execution
    = finished_at - started_at

total latency
    = finished_at - created_at

を計算できます。

ProcessExecutor 自身に duration property はありませんが、metadata から導けます。


35. worker metadata

worker metadata は次です。

worker_id
pid
state
current_job_id
current_handler

idle では current job/handler は None。

running では実行中の job ID と handler 名が入ります。

stopped では再び None。

dead は parent process の replenish が既存 metadata の state を変更します。

この metadata は monitoring や status endpoint の材料にできます。


36. worker ID

worker ID は worker process 起動ごとに UUID4 hex で生成されます。

これは pool slot index ではありません。

worker が死んで replacement が起動すると別 ID になります。

したがって worker ID は process instance identity として扱うのが実装に合っています。


37. stop sentinel

worker loop の停止には特別な文字列:

__PROCESS_EXECUTOR_STOP__

を使います。

worker が Queue からこの値を受け取ると job payload として処理せず loop を抜けます。

loop を抜けた後、worker metadata を stopped に更新します。

stop は job ID を持つ通常 job ではありません。


38. close()

close() は二回目以降を無視する idempotent guard を持ちます。

最初に _closed=True にします。

次に _processes から alive process を集めます。

alive worker 数だけ stop sentinel を Queue に put します。

その後 alive process を順に join します。

最後に Queue を close し Manager を shutdown します。

この順序により、通常 shutdown では worker が Queue から STOP を受け取って loop を終了することを期待しています。


39. close() と running job

STOP sentinel は Queue の item です。

worker が現在 handler を実行中なら、その handler が return/exception して Queue.get() に戻るまで STOP を読みません。

したがって close は running handler を強制中断しません。

現在の process.join() に timeout はありません。

戻らない handler が存在すれば close が待ち続ける可能性があります。


40. context manager

__enter__() は executor 自身を返します。

__exit__() は close() を呼びます。

したがって:

with ProcessExecutor(...) as executor:
    ...

で lifecycle を scope に結び付けられます。

ただし context manager だから running handler を強制終了できるわけではありません。

shutdown semantics は close() と同じです。


41. dead worker detection

_replenish_workers() は parent の _processes table を走査します。

各 Process に:

process.is_alive()

を呼びます。

False の worker ID を dead list に入れます。

その後 dead process を table から pop して join します。

workers metadata が存在すれば snapshot を取り:

state = dead

に更新します。

最後に _processes の要素数が worker_count に戻るまで新 worker を起動します。


42. replenish が呼ばれるタイミング

replenish は常時 background thread で監視しているわけではありません。

source では少なくとも:

submit()
worker()
workers()

から呼ばれます。

したがって worker が死んだ瞬間に独立 watchdog が即座に replacement を作る architecture ではありません。

次に replenish を呼ぶ public operation が実行されたときに検出されます。


43. worker recovery と job recovery は別

worker が handler 実行中に process-level failure で死んだ場合を考えます。

worker metadata には current_job_id が残っている可能性があります。

job metadata は running の可能性があります。

replenish は dead worker を検出して replacement を起動します。

しかし source には:

dead worker の current_job_id を取得
        |
        v
その job を failed にする

という code はありません。

したがって pool size は回復しても running job の state は自動修復されません。

これは重要な failure boundary です。


44. process-level failure

handler 内の Python Exception は try/except で捕捉できます。

しかし次は同じではありません。

OS から process kill
native extension crash
segmentation fault
process の異常終了

これらでは worker loop の exception handling が実行されない場合があります。

その場合 job を failed に更新する code も実行できません。

ProcessExecutor の現在の recovery は worker process の補充までです。


45. factory failure

worker main は job loop に入る前に:

instance = worker_factory(context)

を実行します。

この call は handler execution 用の try/except の外です。

したがって factory または worker_class constructor が失敗した場合、その failure を job metadata の failed として記録する path はありません。

まだ job を取得していないため、特定 job と対応付けることもしていません。


46. startup failure が継続する場合

worker factory が常に失敗する設定を考えます。

parent は Process.start() 自体には成功できます。

child が factory で終了します。

後で replenish がその dead process を検出すると replacement worker を作ります。

しかし configuration が同じなら replacement も同じ factory failure を起こす可能性があります。

source には worker startup retry backoff、startup failure counter、circuit breaker はありません。


47. daemon worker

worker Process は daemon です。

daemon process は親 process に従属します。

また Python multiprocessing の daemon process は child process を生成できません。

そのため handler の中からさらに multiprocessing.Process を起動する nested worker architecture には制約があります。

ProcessExecutor は一段の worker pool を構成するものと考えるのが実装に合っています。


48. Manager

Manager は次の共有 object を作るために使われます。

jobs
workers
state
lock

Manager proxy を使うことで、別 process から Python dictionary に近い interface で metadata を読み書きできます。

一方で shared-memory array の直接アクセスとは異なり、proxy communication の overhead があります。

この executor は metadata と coarse-grained state を共有する設計です。


49. Queue

Queue は job dispatch の channel です。

job metadata の正本そのものを Queue の中だけに置くのではありません。

Queue item は worker が consume すると取り出されますが、jobs dictionary の metadata は残ります。

したがって parent は job 完了後にも job ID から state/result/error を参照できます。

Queue
    execution request の配送

jobs
    execution record の保持

という役割分担です。


50. args / kwargs

submit の args と kwargs は二箇所に使われます。

まず jobs metadata に保存されます。

次に Queue payload にも入ります。

worker は Queue payload の args/kwargs を handler call に展開します。

このため非常に大きな object を arguments にすると、IPC と metadata retention の両方にコストが発生し得ます。

source に arguments の size limit はありません。


51. result の保存

handler return value は jobs Manager dictionary の metadata に保存されます。

したがって result も process 間で Manager が扱える object である必要があります。

非常に大きな numerical array を大量に返す用途では、この設計の IPC overhead を考える必要があります。

ProcessExecutor は shared-memory ndarray transport を特別実装していません。


52. job retention

completed job は jobs dictionary から削除されません。

source には:

delete_job
purge_jobs
TTL
max_history

がありません。

したがって executor lifetime が長く submit 数が増え続ける場合、metadata と result の retention が増えます。

これは後から job ID で結果を取得できる設計と表裏一体です。


53. dead worker metadata retention

dead worker の Process object は _processes から削除されます。

しかし workers Manager dictionary の metadata は削除されません。

state を dead にして残します。

replacement worker は新 ID で追加されます。

したがって workers() は historical dead workers を含み得ます。

current live process table と worker history metadata は別です。


54. logger

constructor は logger argument を受け取り:

self._logger = logger

として保持します。

しかし今回の source 内では _logger を使った logging call はありません。

worker main に logger を渡す code もありません。

したがって現在の実装から保証できるのは「parent executor が logger reference を保持する」ことまでです。

worker-safe logging system が実装されているとは言えません。


55. snapshot helper

_snapshot() は Manager proxy value を通常 dictionary に変換する helper です。

None なら空 dictionary。

それ以外なら dict(value)。

worker は job metadata を更新するとき、現在の metadata snapshot を取り、field を変更して jobs dictionary に再代入します。

つまり nested field を直接 mutate するのではなく、dictionary snapshot を作って置き換える pattern です。


56. timestamp helper

_now() は単純に:

time.time()

を返します。

job metadata の wall-clock timestamp に使われます。

一方 wait timeout の測定には time.monotonic() を使います。

これは用途が異なります。

time.time()
    metadata として記録する時刻

time.monotonic()
    timeout の経過時間測定

です。


57. parent から SharedContext を取得する

shared_context property は parent controller 用に新しい SharedContext object を返します。

中身は worker と同じ:

jobs
workers
state
lock

の proxy reference です。

したがって worker factory だけでなく parent 側も shared state/lock にアクセスできます。

これは parent と worker が同じ controller-specific shared state を参照する設計に利用できます。


58. SharedContext は domain schema を持たない

state に何を保存するかは source で規定されていません。

例えば:

target_position
last_direction
sequence
mode

のような key を利用側が定義できます。

しかしこれらは ProcessExecutor 標準 field ではありません。

そのため application ごとに state schema を明確にする必要があります。

generic dictionary であることは柔軟ですが、型や key の意味は利用側の責務になります。


59. shared state を使うべき場合

worker A で更新した情報を worker B が次の job で必要とする場合、通常の instance attribute だけでは足りません。

Worker A
Controller A
self.target = 100

Worker B
Controller B
self.target は別 object

この target が controller 全体の logical state なら SharedContext.state に置く候補です。

一方、worker A の内部 cache のように worker A だけで完結する state は instance attribute のままで構いません。

shared state は「multiprocessing だから全部共有する」のではなく、意味的に共有すべきものに限定するのが自然です。


60. shared lock を使うべき場合

複数 worker が同じ state を read-modify-write する場合、複数 operation を atomic な critical section として扱う必要があることがあります。

その場合:

with context.lock:
    current = context.state.get("counter", 0)
    context.state["counter"] = current + 1

のようにできます。

ProcessExecutor は state key ごとの lock を提供しません。

一つの generic RLock を提供します。

より細粒度の lock が必要なら利用側 architecture で追加する必要があります。


61. 一つの physical resource と複数 worker

worker-local controller instance が複数あっても、physical resource が一つとは限りませんし、逆も同様です。

例えば単一 TCP connection を一つの device に対して維持する必要があるなら:

Worker Ctrl A ----+
Worker Ctrl B ----+--> resource owner / proxy --> TCP connection x1
Worker Ctrl C ----+

のような構造が必要になる場合があります。

ProcessExecutor は TCP connection ownership を実装していません。

worker process の execution と physical resource の ownership は別問題です。


62. worker_count を増やす意味

worker_count を増やすと、同時に handler を実行できる worker process 数が増えます。

しかしそれが安全な parallelism かどうかは handler が使う resource に依存します。

CPU calculation なら CPU core 数や IPC overhead が関係します。

device control なら device protocol が concurrent command を許すかが関係します。

single connection なら serialization layer が必要かもしれません。

ProcessExecutor は worker_count の適正値を自動判定しません。


63. CPU-bound job

multiprocessing process を使うため、Python thread とは異なる execution process になります。

CPU-bound Python code を複数 worker で実行する用途にも構造上は利用できます。

ただし job arguments/result を Queue/Manager 経由で転送する overhead があります。

非常に小さな calculation を大量に投げる場合、process IPC cost が支配的になる可能性があります。

source は batching を実装していません。


64. I/O-bound job

handler が blocking I/O を行う場合も worker process 上で実行できます。

caller process は submit 後すぐ job ID を受け取れます。

一方、同じ physical I/O resource を複数 worker が共有する場合の排他や connection ownership は executor core では決めません。

そのため I/O-bound だから worker_count を単純に増やせばよいとは限りません。


65. 上位同期 API

ProcessExecutor の内部は job-based ですが、上位 public API を job-based にする必要はありません。

class MultiController:
    def __init__(self):
        self._executor = ProcessExecutor(
            worker_class=Controller,
            worker_count=2,
        )

    def move(self, position):
        job_id = self._executor.submit("move", position)
        return self._executor.wait(job_id)

とすれば caller からは通常の同期 method に見えます。

この形では multiprocessing infrastructure を facade の内部へ隠せます。


66. 上位非同期 API

job ID を public API に出したい場合は:

def start_move(self, position):
    return self._executor.submit("move", position)

def move_state(self, job_id):
    return self._executor.state(job_id)

のようにできます。

HTTP server なら request と long-running job の lifetime を分離できます。

ProcessExecutor 自体は HTTP を知りません。

job model だけを提供します。


67. FastAPI 等との境界

ProcessExecutor は web framework ではありません。

request parsing、authentication、response schema、HTTP status code は扱いません。

しかし:

POST /jobs
    -> submit()
    -> job_id

GET /jobs/{id}
    -> job()

GET /jobs/{id}/result
    -> result()

のような上位設計を構築する材料になります。

handler 名を外部入力から直接渡す場合は、前述のとおり allowlist を上位 layer で設けるべきです。


68. unknown job ID

API ごとに unknown ID の挙動が違います。

state(id)  -> None
job(id)    -> None
result(id) -> KeyError
wait(id)   -> KeyError

この差は上位 API wrapper を作るときに意識する必要があります。

例えば HTTP 404 へ変換するなら、None と KeyError の両方を同じ application-level not-found に正規化できます。


69. failed result の exception

failed job に result() または wait() を呼ぶと:

RuntimeError(str(metadata.get("error")))

です。

error dictionary 全体が string 化されます。

元 exception の traceback は job metadata に残っています。

詳細 diagnostics が必要なら:

metadata = executor.job(job_id)
metadata["error"]["traceback"]

を参照できます。


70. result object と snapshot

job() が返す metadata は snapshot ですが、その中の result object 自体の mutability について ProcessExecutor は追加 copy を行いません。

Manager dictionary から取り出した metadata を dict 化して返すだけです。

deep copy API ではありません。

複雑な mutable result の semantics は multiprocessing/Manager serialization と object type に依存します。


71. submit arguments の検証範囲

submit は handler の type と空文字だけを検証します。

args/kwargs の type validation はしません。

handler signature との整合性も submit 時には検証しません。

そのため invalid arguments は worker process で handler call を行った時点で failed job になります。

「投入前に即時 validation error を返す」executor ではありません。


72. worker class constructor arguments

worker_args=None は空 tuple に変換されます。

worker_kwargs=None は空 dictionary に変換されます。

worker_class path では _WorkerClassFactory がそれらを保持します。

各 worker process は同じ configured args/kwargs を使って独立 instance を生成します。

worker ごとに異なる constructor arguments を自動割当する mechanism はありません。

必要なら worker_factory 側で process context や外部 configuration を使う設計が必要です。


73. factory に渡る worker ID

worker_factory の signature は:

worker_factory(context)

です。

worker ID 自体は factory argument に含まれません。

SharedContext.workers に worker metadata が登録されるのは factory call の後です。

したがって factory が「自分の worker ID」を直接受け取る contract ではありません。

worker-specific identity が constructor に必要な場合は現在の factory interface だけでは直接提供されません。


74. worker metadata 登録前の failure

factory が成功して初めて worker metadata の idle record が作られます。

factory が失敗した場合、workers dictionary にその worker ID の metadata が一度も作られない可能性があります。

replenish は parent _processes から dead process を検出します。

metadata が None なら dead state update は行いません。

したがって startup failure worker が workers() の historical metadata に必ず残るとは限りません。


75. worker stop metadata

STOP を受け取ると while loop を break します。

その後 worker metadata を stopped にします。

これは normal shutdown path です。

dead は parent が abnormal/non-alive process を検出したときの state です。

normal STOP -> stopped

unexpected death detected by parent -> dead

という区別があります。


76. close 後の metadata

close は Manager.shutdown() を行います。

そのため close 後に shared Manager proxy を通常どおり利用し続ける lifecycle は想定できません。

submit は _closed を明示的に検査します。

他の read API すべてが _closed を検査するわけではありませんが、Manager 自体が終了しているため close は executor の利用終了点として扱うべきです。


77. close の idempotency

close の先頭は:

if self._closed:
    return

です。

そのため context manager の exit と明示 close が重なっても二重 shutdown を避けられます。

executor.close()
executor.close()

の二回目は何もしません。


78. result の None

handler が正常に None を return した場合、job state は succeeded で result は None です。

failed job も metadata result は None です。

したがって result field だけでは成功/失敗を区別できません。

必ず state と組み合わせて解釈します。

result() は state を確認してから result を返すため、この区別を内部で行います。


79. error の None

queued/running/succeeded の正常 path では error は None です。

failed では error dictionary です。

handler が succeeded した場合、result 保存時に error=None を明示的に設定します。

job metadata の error field は exception transport 用です。

domain-level warning や validation message 用 field ではありません。


80. metadata update pattern

worker は jobs dictionary から current metadata を snapshot し、field を変更して全 dictionary を再代入します。

metadata = _snapshot(jobs.get(job_id))
metadata["state"] = "running"
...
jobs[job_id] = metadata

同様に succeeded/failed update も行います。

この pattern では一つの metadata update は dictionary replacement として Manager に送られます。

複数主体が同じ job metadata を同時更新する拡張を将来行う場合、競合 semantics を検討する必要があります。

現在は主にその job を取得した worker が execution fields を更新します。


81. jobs dictionary と lock

ProcessExecutor 自身の job metadata update は SharedContext.lock を明示的には使っていません。

Manager dictionary operation と snapshot replacement を利用しています。

SharedContext.lock は generic controller-side shared state 用として提供されています。

将来複数 process が同じ job metadata を複雑に read-modify-write する機能を追加するなら、atomicity を別途検討する必要があります。


82. workers dictionary と lock

worker metadata update も lock を明示的には使いません。

worker 自身が自分の worker ID の record を置き換えます。

parent replenish は dead worker の record を state=dead に更新します。

worker death と parent detection の timing によって metadata の最終状態がどう見えるかは process scheduling に依存し得ます。


83. Queue の STOP 数

close は alive process 数を数え、その数だけ STOP を put します。

これは各 alive worker が一つずつ STOP を consume することを意図しています。

dead process 数ではなく alive process 数です。

ただし STOP は通常 job と同じ Queue に入るため、すでに Queue に通常 job が残っていれば FIFO 的にそれらの後で処理される可能性があります。

close は queued job を purge して即停止する実装ではありません。


84. close と queued jobs

close 時に Queue に残っている通常 job を削除する code はありません。

STOP を alive worker 数だけ追加します。

したがって Queue ordering により、worker が STOP より前に queued job を取得して処理する可能性があります。

「close を呼んだ瞬間に未開始 job を cancel する」という semantics ではありません。


85. submit と close の concurrency

source は _closed flag に対する thread lock を持ちません。

submit と close を同一 parent process の複数 thread から同時に呼ぶ場合の厳密な thread-safety contract は source に明記されていません。

ProcessExecutor の主たる concurrency model は worker process 側です。

parent-side multi-thread concurrent API calls を保証する library と解釈するには追加検証が必要です。


86. multiprocessing start method と platform

get_context(start_method) を使うため、明示 start method を一つの executor 内で一貫して利用できます。

macOS など spawn が一般的な環境では、worker class/factory が import 可能であることや main guard が重要になります。

if __name__ == "__main__":
    main()

ProcessExecutor はこの guard を自動生成できません。

利用 application 側の責務です。


87. pickle と worker factory

spawn では worker process を起動するために target arguments を serialization する必要があります。

worker factory や _WorkerClassFactory が process boundary を越えます。

module top-level に定義された class/factory の方が扱いやすいのは Python multiprocessing の一般的な性質です。

ProcessExecutor source 自体は cloudpickle 等の追加 serializer を使っていません。


88. physical device object を直接渡す問題

すでに open 済みの device object、socket、serial object 等を parent から worker_args に入れれば常に安全に共有できる、とは source から言えません。

process boundary と start method に依存します。

また複数 worker で同じ physical connection をどう serialize するかも別問題です。

そのため resource は worker 内で生成するか、明示的な IPC owner/proxy architecture を設計する方が責務を明確にできます。


89. worker-local connection

worker_class constructor が connection を開く設計なら、worker_count 個の worker がそれぞれ constructor を実行します。

つまり connection も worker ごとに作られる可能性があります。

worker_count = 4
Controller constructor opens TCP
        ->
TCP connection が最大4本

これが正しいかどうかは device semantics に依存します。

ProcessExecutor は connection cardinality を制御しません。


90. single resource broker

physical resource を一つにしたい場合、resource owner を一つの process/service に置き、worker-local controller は proxy を使う architecture が考えられます。

worker 1 --+
worker 2 --+--> proxy --> owner --> device
worker 3 --+

これは ProcessExecutor source の機能そのものではありません。

ただし executor と resource ownership を別 layer にすることで、generic worker scheduling と device-specific serialization を分離できます。


91. job state machine

source から直接確認できる job state transition は:

submit
  |
  v
queued
  |
  | worker receives
  v
running
  |
  +-- handler returns -----> succeeded
  |
  `-- Exception ----------> failed

cancelled、retrying、timeout、lost といった state はありません。

worker process death で running job が自動的に別 state へ移る transition もありません。


92. worker state machine

worker state は概念的に:

factory success
      |
      v
    idle
      |
      | job
      v
   running
      |
      +-- job complete --> idle
      |
      `-- STOP is read only after returning to queue loop

idle -- STOP --> stopped

parent が process death を検出した場合 metadata は dead になります。

dead は worker 自身が正常 loop で設定する state ではありません。


93. state machine にない状態を作らない

上位 application が job state を UI に表示するとき、ProcessExecutor の state と application 独自 state を混ぜない方が明確です。

例えば physical device が moving であることと executor job が running であることは別概念です。

executor state: running

device state: moving

handler が device command を発行してすぐ return する設計なら executor job は succeeded でも device は moving の可能性があります。

domain state は controller 側で別に表現する必要があります。


94. handler の粒度

ProcessExecutor は method call 一回を job とします。

しかしどの粒度の method を submit するかは利用側次第です。

send_one_byte()

のような極端に細かい operation を大量 submit すると IPC overhead が大きくなります。

measure_sample()

のような意味のある coarse-grained operation を一 job にする方が execution layer の価値が出る場合があります。

source は粒度を強制しません。


95. long-running handler

long-running handler は一つの worker をその間占有します。

worker_count=4 で4個の long-running handler が走れば、5個目の job は Queue で待ちます。

ProcessExecutor は preemption を行いません。

running handler を途中で別 worker へ移動することもありません。


96. starvation と fairness

Queue は generic multiprocessing Queue です。

source に handler priority や per-client quota はありません。

長時間 job と短時間 job を同じ Queue に入れると、worker availability によって短時間 job が待つ可能性があります。

厳密な scheduling fairness policy は ProcessExecutor が提供する機能ではありません。


97. backpressure

submit は Queue.put を行います。

Queue 作成時に maxsize を指定していません。

source に outstanding job 数を制限する backpressure API はありません。

producer が worker processing rate より速く submit し続ける場合、queued work と jobs metadata が増えます。

長時間 server で利用する場合は上位 layer で admission control を考える余地があります。


98. persistence

jobs/workers/state は Manager process の memory 上にあります。

close で Manager.shutdown() します。

persistent database ではありません。

parent process が終了した後も job history を保持する機能はありません。

durable task queue として利用する設計ではありません。


99. remote execution

worker は同一 Python multiprocessing environment の Process です。

network remote worker registration はありません。

message broker もありません。

複数 host に分散する executor ではありません。

一台の host 内で process isolation を使う execution layer です。


100. ProcessExecutor の適用範囲

source から見た適用範囲は、既存 Python class の method を local multiprocessing worker で実行し、その job と worker の状態を parent から追跡したいケースです。

特に有効なのは:

既存 class を変更したくない
worker process を再利用したい
job ID が必要
state/result/error を後から取得したい
複数 worker が必要
一部 state を process 間共有したい

という要求が組み合わさる場合です。


101. ProcessExecutor が現在持たないもの

source に存在しない機能を明確にすると境界が分かります。

cancel
retry policy
priority
job TTL
job purge
persistent storage
remote worker
dynamic resize
worker affinity
handler allowlist
execution deadline
startup readiness barrier
running-job recovery
metrics exporter
automatic resource broker

これらが必要なら追加設計が必要です。


102. 実例: 単純な計算

class MathWorker:
    def square(self, value):
        return value * value
with ProcessExecutor(
    worker_class=MathWorker,
    worker_count=4,
) as executor:
    ids = [
        executor.submit("square", value)
        for value in range(10)
    ]

    values = [
        executor.wait(job_id)
        for job_id in ids
    ]

job ID と submit order は保持できますが、実際の completion order は worker scheduling に依存します。


103. 実例: state を観測する

job_id = executor.submit("run", argument)

print(executor.state(job_id))
print(executor.job(job_id))

job metadata を定期的に取得すれば上位 UI で status を表示できます。

ただし job() が返すのは snapshot なので、同じ dictionary を保持していても自動更新されません。

再取得が必要です。


104. 実例: worker を観測する

workers = executor.workers()

for worker_id, metadata in workers.items():
    print(
        worker_id,
        metadata["pid"],
        metadata["state"],
        metadata["current_handler"],
    )

これにより現在 running handler を確認できます。

workers() 呼び出し自体が replenish を起こす可能性がある点には注意が必要です。


105. 実例: shared counter

class CounterWorker:
    def __init__(self, state, lock):
        self._state = state
        self._lock = lock

    def increment(self):
        with self._lock:
            value = self._state.get("count", 0)
            value += 1
            self._state["count"] = value
            return value


def factory(context):
    return CounterWorker(
        state=context.state,
        lock=context.lock,
    )

この例では worker-local attribute ではなく、全 worker が同じ state["count"] を参照します。

read-modify-write を lock で囲みます。


106. 実例: parent から shared state を読む

context = executor.shared_context

with context.lock:
    current = context.state.get("count", 0)

shared_context property により parent 側も同じ Manager proxy にアクセスできます。

worker と parent の双方が同じ state schema を使う場合に利用できます。


107. 実例: 同期 facade

class MultiController:
    def __init__(self):
        self._executor = ProcessExecutor(
            worker_class=Controller,
            worker_count=2,
        )

    def query(self, command):
        job_id = self._executor.submit("query", command)
        return self._executor.wait(job_id)

    def close(self):
        self._executor.close()

caller は ProcessExecutor を直接知る必要がありません。

既存の同期 interface を保ったまま実処理を worker process へ移せます。


108. 実例: job-oriented facade

class JobController:
    def __init__(self):
        self._executor = ProcessExecutor(
            worker_class=Controller,
            worker_count=2,
        )

    def submit_query(self, command):
        return self._executor.submit("query", command)

    def job(self, job_id):
        return self._executor.job(job_id)

    def result(self, job_id):
        return self._executor.result(job_id)

こちらは job ID を application API に露出する形です。

server や GUI の background operation と相性があります。


109. 実例: timeout 後に再確認する

job_id = executor.submit("long_operation")

try:
    result = executor.wait(job_id, timeout_sec=1.0)
except TimeoutError:
    print(executor.state(job_id))

timeout は execution cancellation ではないため、後で:

print(executor.state(job_id))

と再確認できます。

handler が完了していれば succeeded/failed に変わっています。


110. 実例: failed job の traceback

job_id = executor.submit("operation")

try:
    executor.wait(job_id)
except RuntimeError:
    metadata = executor.job(job_id)
    print(metadata["error"]["type"])
    print(metadata["error"]["message"])
    print(metadata["error"]["traceback"])

worker traceback は error metadata に保存されます。

parent で元 exception object を再構築するわけではありません。


111. 実例: close

executor = ProcessExecutor(
    worker_class=Controller,
    worker_count=2,
)

try:
    ...
finally:
    executor.close()

または:

with ProcessExecutor(
    worker_class=Controller,
    worker_count=2,
) as executor:
    ...

です。

context manager の方が lifecycle を明示しやすい構造です。


112. API Reference

この Pages の API Reference は手書きしません。

docs/build.sh が build ごとに:

sphinx-apidoc \
    -o docs/api \
    src/process_executor \
    -f \
    --module-first

を実行します。

生成された .rst を Sphinx autodoc が処理します。

これにより source code の:

module
class
method
signature
type annotation
docstring
inheritance
source view

を機械的に Pages に反映します。


113. 手動本文と自動 API の役割

自動 API Reference が得意なのは「何という class/method があり、signature と docstring が何か」です。

一方、次は source の個々の docstring だけでは把握しにくい内容です。

worker-local instance の寿命
jobs と Queue の役割の違い
_processes と workers metadata の違い
worker recovery と job recovery の違い
wait timeout と execution cancellation の違い
SharedContext.state の意味
single physical resource と worker_count の関係

これらをこの手動本文で説明します。

したがって手動 Pages は API 一覧のコピーではありません。


114. documentation build

ローカルでは repository root から:

bash docs/build.sh

を実行します。

script は:

docs/api を削除
docs/_build を削除
sphinx-apidoc で API stub 生成
sphinx-build で HTML 生成
.nojekyll 作成

を行います。

HTML 出力先は:

docs/_build/html/

です。


115. GitHub Pages workflow

成果物では指定どおり:

github/workflows/pages.yml

として出力しています。

実際の GitHub repository では GitHub Actions の規則に従って:

.github/workflows/pages.yml

に配置する内容です。

workflow は main push と手動実行を trigger にし、Python と documentation dependency を install し、docs/build.sh を実行し、生成 HTML を Pages artifact として deploy します。


116. source と documentation の同期

API reference は build 時に source を解析するため、public API の追加・削除は自動生成部分へ反映されます。

一方、この本文は execution semantics を説明するため、内部挙動を変更した場合には人間が更新する必要があります。

例えば:

job cancellation を追加
worker crash recovery を追加
readiness barrier を追加
job purge を追加

した場合、自動 API には method が現れますが、その設計上の意味は本文へ追記する必要があります。


117. 設計変更時の確認軸

job metadata を変える場合:

submit
worker running update
success/failure update
state
job/jobs
result/wait

を一緒に確認します。

worker metadata を変える場合:

worker startup
running
idle
stopped
dead
worker/workers
replenish

を確認します。

SharedContext を変える場合:

parent shared_context property
worker main
worker_factory
controller-side usage

を確認します。

lifecycle を変える場合:

STOP
Queue
join
Manager shutdown
dead worker
queued/running job

を確認します。


118. 現在の実装を一枚で読む

ProcessExecutor.__init__
        |
        +--> get_context
        +--> Manager
        |     +--> jobs
        |     +--> workers
        |     +--> state
        |     `--> RLock
        |
        +--> Queue
        |
        `--> start workers
                |
                v
          _worker_main
                |
                +--> SharedContext
                +--> factory(context)
                +--> worker = idle
                |
                `--> Queue.get loop
                        |
                        +--> STOP -> stopped
                        |
                        `--> job
                             |
                             +--> worker running
                             +--> job running
                             |
                             +--> getattr(instance, handler)
                             +--> handler(*args, **kwargs)
                             |
                             +--> succeeded + result
                             |        or
                             `--> failed + error
                                      |
                                      v
                                  worker idle

これが現在の source code の中心構造です。


119. まとめ

ProcessExecutor は、既存 Python class を multiprocessing worker 上で再利用するための generic execution layer です。

最も重要な設計上の分離は次です。

method の意味
    -> worker-local class

method をどの process で実行するか
    -> ProcessExecutor

job の execution record
    -> jobs

worker の execution record
    -> workers

controller 全体で共有する state
    -> SharedContext.state

process-safe critical section
    -> SharedContext.lock

この分離によって、既存クラスへ multiprocessing infrastructure を埋め込まず、worker pool、job ID、state/result/error、worker observation、shared context、shutdown を共通化できます。

同時に、現在の source が持たない cancellation、priority、persistent queue、remote execution、automatic job recovery 等を機能として誤認しないことも重要です。

API の表面的な一覧は source から機械生成し、Pages 本文ではこの execution model と責務境界を説明する構成にしています。

120. Source-derived API inspection

__init__

Source line: 182

実装中に現れる call:

  • ValueError

  • _WorkerClassFactory

  • multiprocessing.get_context

  • self._manager.RLock

  • self._manager.dict

  • self._mp_context.Manager

  • self._mp_context.Queue

  • self._start_workers

明示的な raise:

  • ValueError('worker_count must be 1 or greater')

  • ValueError('worker_factory or worker_class must be specified')

  • ValueError('worker_factory and worker_class cannot be used together')

  • ValueError('worker_args is only valid with worker_class')

  • ValueError('worker_kwargs is only valid with worker_class')

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

shared_context

Source line: 248

Docstring:

Return the shared context for the parent controller.

実装中に現れる call:

  • SharedContext

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

submit

Source line: 257

Docstring:

Submit a handler job and immediately return its job ID.

実装中に現れる call:

  • RuntimeError

  • TypeError

  • ValueError

  • _now

  • isinstance

  • self._job_queue.put

  • self._replenish_workers

  • uuid.uuid4

明示的な raise:

  • RuntimeError('ProcessExecutor is already closed')

  • TypeError('handler must be str')

  • ValueError('handler must not be empty')

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

state

Source line: 296

Docstring:

Return a job state.

実装中に現れる call:

  • metadata.get

  • self.job

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

job

Source line: 305

Docstring:

Return a snapshot of one job.

実装中に現れる call:

  • dict

  • self._jobs.get

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

jobs

Source line: 314

Docstring:

Return snapshots of all jobs.

実装中に現れる call:

  • dict

  • self._jobs.items

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

worker

Source line: 323

Docstring:

Return a snapshot of one worker.

実装中に現れる call:

  • dict

  • self._replenish_workers

  • self._workers.get

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

workers

Source line: 333

Docstring:

Return snapshots of all workers.

実装中に現れる call:

  • dict

  • self._replenish_workers

  • self._workers.items

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

result

Source line: 343

Docstring:

Return the result of a completed job.

実装中に現れる call:

  • KeyError

  • RuntimeError

  • metadata.get

  • self.job

  • str

明示的な raise:

  • KeyError(job_id)

  • RuntimeError(str(metadata.get('error')))

  • RuntimeError('job is not completed')

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

wait

Source line: 360

Docstring:

Explicitly wait for one job and return its result.

実装中に現れる call:

  • KeyError

  • RuntimeError

  • TimeoutError

  • metadata.get

  • self.job

  • str

  • time.monotonic

  • time.sleep

明示的な raise:

  • KeyError(job_id)

  • RuntimeError(str(metadata.get('error')))

  • TimeoutError('waiting for job timed out')

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

close

Source line: 391

Docstring:

Stop workers and shut down shared resources.

実装中に現れる call:

  • alive_processes.append

  • process.is_alive

  • process.join

  • self._job_queue.close

  • self._job_queue.put

  • self._manager.shutdown

  • self._processes.values

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

__enter__

Source line: 412

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

__exit__

Source line: 415

実装中に現れる call:

  • self.close

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

_start_workers

Source line: 418

Docstring:

Start the requested number of workers.

実装中に現れる call:

  • len

  • self._start_worker

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

_start_worker

Source line: 423

Docstring:

Start one generic worker.

実装中に現れる call:

  • process.start

  • self._mp_context.Process

  • uuid.uuid4

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

_replenish_workers

Source line: 443

Docstring:

Remove dead workers and restore the configured worker count.

実装中に現れる call:

  • dead_worker_ids.append

  • dict

  • len

  • process.is_alive

  • process.join

  • self._processes.items

  • self._processes.pop

  • self._start_worker

  • self._workers.get

この一覧は supplied source の AST から抽出した補助情報です。API の正式表示は Sphinx autodoc が build 時に source から生成します。

SharedContext

SharedContext は四つの constructor argument を同名 attribute として保持します。

  • jobs

  • workers

  • state

  • lock

追加の method や validation は supplied source にはありません。