Sentinels - Control Signals in pychanio
In complex concurrent systems, it’s not enough to send only data between goroutines-we often need to signal intent, such as shutdown, cancellation, or heartbeat. pychanio offers a clean and idiomatic way to accomplish this using sentinels.
What are Sentinels?
A Sentinel is a unique, singleton-like object used to convey control messages through channels. Unlike regular data, sentinels:
Are identity-comparable (
msg is DONE
)Are self-documenting (e.g.,
<Signal: CANCEL>
)Allow for typed control flow without magic strings
These are critical for implementing robust shutdown signals, cancellation of operations, and keep-alive logic in pipelines.
Predefined Sentinels in pychanio
pychanio provides three built-in signals in pychanio.sentinels
:
DONE
Indicates completion or shutdown
CANCEL
Requests cancellation of operation
HEARTBEAT
Used for liveness or ping signals
from pychanio.sentinels import DONE, CANCEL, HEARTBEAT
Example: Sending a Shutdown Signal
from pychanio import chan, close
from pychanio.sentinels import DONE
async def worker(ch):
async for msg in ch:
if msg is DONE:
print("Received shutdown signal")
break
print(f"Working on: {msg}")
ch = chan()
await (ch << "task-1")
await (ch << DONE)
close(ch)
await worker(ch)
Detecting Sentinels in Pipelines
Use the utility function is_signal(obj)
to identify if a received message is a sentinel:
from pychanio.sentinels import is_signal
async for msg in ch:
if is_signal(msg):
print(f"Received control signal: {msg}")
else:
process(msg)
This avoids hard-coding identity checks (msg is ...
) and lets you apply generic logic across all sentinels.
Fan-In Use Case with Select and Sentinels
Sentinels pair especially well with select()
to provide structured exits for pipelines:
from pychanio import select
from pychanio.sentinels import DONE
async def fan_in_consumer(ch1, ch2, done):
while True:
result = await select(
(done >> DONE, lambda val, ok: DONE),
(ch1 >> None, lambda val, ok: val if ok else None),
(ch2 >> None, lambda val, ok: val if ok else None),
)
if result is DONE:
print("Shutdown requested")
break
print(f"Received: {result}")
Why Use Sentinels Instead of Strings?
While you can use plain strings like "done"
or "cancel"
, sentinels provide stronger guarantees:
✅ Identity comparison is faster and safer than string matching.
✅ Singletons prevent accidental overwrites.
✅ Self-describing for debugging and logging.
✅ Extensible: define your own!
Defining Custom Sentinels
You can define your own using the Sentinel
class:
from pychanio.sentinels import Sentinel
RETRY = Sentinel("RETRY")
Use this in the same way as built-ins.
Summary
DONE
, CANCEL
, HEARTBEAT
Built-in lifecycle signals
Sentinel(name)
Create custom signal objects
is_signal(x)
Generalized signal detection
Identity comparison
Faster, safer than string matches
Sentinels are a key part of PyChan’s philosophy of structured concurrency and predictable control flow.
Up Next
In the next chapters, we’ll explore Blocking and non blocking operations and real-world concurrency patterns using all the pieces we’ve explored so far-channels, select blocks, goroutines, and sentinels.
Last updated