bocpy documentation

bocpy is a Python library implementing Behavior-Oriented Concurrency (BOC). Programmers wrap shared data in cowns (concurrently-owned objects) and schedule behaviors with the @when decorator; the runtime runs each behavior once all of its required cowns are available, with deadlock freedom guaranteed by construction. On Python 3.12 and newer, behaviors execute in parallel across worker sub-interpreters that each have their own GIL.

For a hands-on introduction, see the BOC tutorial, the project README, and the runnable examples. The API page below documents every public symbol.

A taste of BOC

The snippet below — a trimmed version of bocpy-bank — shows the core concepts: data wrapped in Cowns, a behavior scheduled with when() that takes exclusive access to two cowns at once, and wait() blocking the main thread until all behaviors have completed. The runtime acquires src and dst in a deadlock-free order, so transfer can safely mutate both accounts.

from bocpy import Cown, wait, when


class Account:
    def __init__(self, name, balance):
        self.name = name
        self.balance = balance


def transfer(src: Cown[Account], dst: Cown[Account], amount: float):
    # `@when` schedules `_` to run once both cowns are available.
    # Inside, src.value and dst.value can be mutated safely —
    # no other behavior can touch either account at the same time.
    @when(src, dst)
    def _(src, dst):
        print(f"  transfer: {src.value.name} -> {dst.value.name} ({amount})")
        if src.value.balance >= amount:
            src.value.balance -= amount
            dst.value.balance += amount

    @when(dst)
    def _(dst):
        print(f"  {dst.value.name} now has {dst.value.balance}")


alice = Cown(Account("Alice", 100))
bob = Cown(Account("Bob", 0))

print("scheduling first transfer")
transfer(alice, bob, 40)
print("scheduling second transfer")
transfer(bob, alice, 10)
print("main thread reaches wait()")

wait()  # block until every scheduled behavior has finished
print("all behaviors complete")

Running it prints something like:

$ python bank.py
scheduling first transfer
scheduling second transfer
main thread reaches wait()
  transfer: Alice -> Bob (40)
  Bob now has 40
  transfer: Bob -> Alice (10)
  Alice now has 70
all behaviors complete

Note how the scheduling lines all print before any behavior body runs: @when returns immediately, and the runtime only fires each behavior once its cowns are free. The two transfers serialise on the Alice/Bob cowns, so their effects are interleaved in a deadlock-free, data-race-free order chosen by the runtime.

For cross-behavior shared state see Noticeboard. For lower-level Erlang-style send / receive channels see Messaging.

Indices and Tables