Actions
Actions are the entry points for the model checker. They are automatically invoked by the model checker to explore the state space.
action Init:
# Initialize state variables
counter = 0
items = []
atomic action Increment:
if counter >= 10:
return
counter = counter + 1
Actions can have optional modifiers that control execution flow and fairness:
[flow_modifier] [fairness_modifier] action ActionName:
Modifier Order (both optional):
- Flow modifier:
atomicorserial(default:serial) - Fairness modifier:
fair,fair<weak>, orfair<strong>(default: unfair) actionkeyword- Action name
Examples:
# No modifiers - serial, unfair
action Process:
counter = counter + 1
# Flow only - atomic, unfair
atomic action AtomicUpdate:
x = x + 1
y = y + 1
# Fairness only - serial, weakly fair
fair action Progress:
done = True
# Both modifiers - atomic, weakly fair
atomic fair action ProduceMessage:
require len(queue) < MAX_SIZE
queue.append(message)
# Both modifiers - atomic, strongly fair
atomic fair<strong> action SelectValue:
value = fair<strong> oneof options
Common mistake: Using fair atomic action instead of atomic fair action (syntax error)
- No parameters: Actions cannot take parameters (no parentheses)
- Automatic invocation: Model checker calls actions automatically
- Special Init action: Called once at the start
- Default flow: Serial (yields between statements)
- State scope: Variables in
Initbecome global state
The Init action is special:
- Called exactly once at the beginning
- Initializes global state variables
- All variables defined in
Initscope are part of the state - Must be named exactly
Init
action Init:
# These become state variables
x = 0
items = []
config = {"key": "value"}
Actions can be conditionally enabled:
action DoSomething:
# Option 1: if-return pattern
if not condition:
return
# Action body
action DoSomethingElse:
# Option 2: require statement
require condition
# Action body
Difference:
if-return: Action executes but does nothingrequire: Action is disabled (not executed at all)
How actions become enabled:
- Start disabled: Every action starts with
enabled=False - Enabled by execution: Action becomes enabled when it executes a simple Python statement (assignment, method call)
- Control flow doesn’t enable:
if,for,while,requirealone don’t enable - Disabled by require:
require Falsedisables and exits immediately
Examples:
action Example1:
if condition:
x = 1 # This enables the action
# Action is enabled if condition is True
action Example2:
require condition # Disables if False
# Action never becomes enabled if condition is False
action Example3:
for i in range(10):
pass # for loop doesn't enable, but pass does
# Action is enabled
action Example4:
if False:
x = 1 # Never executed
# Action stays disabled (no statement executed)
Simple Python statements that enable actions:
- Assignments:
x = 1,list.append(2) - Function calls with assignment:
result = func() passstatement
Statements that DON’T enable:
- Control flow:
if,elif,else,for,while - Flow control:
break,continue,return - Guards:
require - Non-determinism:
oneof/any(but the assignmentx = oneof ...does enable)
Critical pitfall:
# ⚠️ This action is ALWAYS enabled (even when condition is False)
action Pitfall:
is_ready = (condition == True) # Assignment enables!
if is_ready:
process()
# ✅ Correct version - only enabled when condition is True
action Fixed:
require condition
process()
Advanced pattern: require in the middle of an action (after a yield point) can coordinate with other actions. See “Action Coordination with Require and Yield” in Quick Reference.
Use pass to explicitly enable an action without changing state:
atomic action NoOp:
pass # Unconditionally enabled, does nothing
The simplest FizzBee spec with a single action that does nothing meaningful
Demonstrates: basic action syntax and minimal state initialization
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 1, queued: 0
Valid Nodes: 1 Unique states: 1
IsLive: true
PASSED: Model checker completed successfully
A single atomic action that modifies state
Demonstrates: atomic modifier and simple state transitions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 101, queued: 0
Valid Nodes: 101 Unique states: 101
IsLive: true
PASSED: Model checker completed successfully
Multiple independent actions operating on the same state
Demonstrates: how multiple actions create state transitions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 201, queued: 0
Valid Nodes: 201 Unique states: 201
IsLive: true
PASSED: Model checker completed successfully
Demonstrates how Init action initializes state variables All variables defined in Init become part of the state space NOTE: Unbounded state space - see 0002 for details on state explosion
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 101, queued: 0
Valid Nodes: 101 Unique states: 101
IsLive: true
PASSED: Model checker completed successfully
Global constants defined at top level, used in Init
Demonstrates: global statements and constant access
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 4, queued: 0
DEADLOCK detected
FAILED: Model checker failed
------
Init
--
state: {"counter":0,"limit":3}
------
Increment
--
state: {"counter":1,"limit":3}
------
Increment
--
state: {"counter":2,"limit":3}
------
Increment
--
state: {"counter":3,"limit":3}
------
Using require for guard clauses
Demonstrates: require statement, explicit enabling conditions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 2, queued: 0
Valid Nodes: 2 Unique states: 2
IsLive: true
PASSED: Model checker completed successfully
Using pass to prevent deadlocks at terminal states
Demonstrates: pass statement prevents stuttering deadlock
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 6, queued: 0
Valid Nodes: 6 Unique states: 6
IsLive: true
PASSED: Model checker completed successfully
Comparing implicit and explicit guard patterns
Demonstrates: if-return vs require for enabling conditions
|
|
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 4, queued: 0
Valid Nodes: 4 Unique states: 4
IsLive: true
PASSED: Model checker completed successfully
The two supported require placements around parallel blocks
Demonstrates: require above the block guards the action; require in a nested atomic/serial branch guards that branch; a require directly in a parallel block is a model error
Uses a
fizz.yamlconfig file (shown below); run locally withfizz.
# Example: require with Parallel Blocks
# require cannot be a DIRECT statement of a parallel block (the model
# checker reports a clean error): parallel statements are unordered, so
# there is no defined point at which the condition would be checked.
# The two supported patterns, both exercised here:
# 1. require above the parallel block - guards the whole action
# 2. require wrapped with its dependent statements in an atomic/serial
# branch - guards just that branch
action Init:
a = 0
b = 0
ready = False
atomic action MakeReady:
ready = True
action NoOp:
pass
# Pattern 1: guard the whole action
action GuardedAction:
require ready and a < 2
parallel:
a = a + 1
b = (b + 1) % 3
# Pattern 2: guard one branch
parallel action BranchGuard:
atomic:
require b > 0
a = (a + 1) % 3
serial:
b = (b + 1) % 3
fizz.yaml:
# The branch guard (require b > 0) has await semantics after a yield:
# when b cycles back to 0, in-flight threads hold until it becomes true
# again, and two held threads fill max_concurrent_actions - a genuine
# await-starvation deadlock, orthogonal to the require-placement patterns
# this example demonstrates.
deadlock_detection: false
Expected result (from fizz):
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2} deadlock_detection:false
Nodes: 656, queued: 0
Skipping dotfile generation. Too many Nodes: 656
Valid Nodes: 656 Unique states: 497
IsLive: true
PASSED: Model checker completed successfully