Skip to content

Commit 946f86e

Browse files
Stephanie Wehnerclaude
andcommitted
Fix nativeMode race conditions: add PB retry logic to setup_local and _wait_ready guards to server nodes
- Replace blocking time.sleep(3) in setup_local with _connect_with_retry(), mirroring the vnode retry pattern (conn_retry_time/conn_max_retries settings) - Fix missing return after reactor.stop() in _init_register (caused UnboundLocalError on virtRoot when connection failed) - Add _wait_ready() polling guard to all nativeMode server node files so remote calls wait for virtRoot/qReg to be initialized - Add flush=True to server node prints (fixes stdout buffering when output is redirected) - Add sleep 5 after simulaqron start in all nativeMode run.sh scripts - Update Makefile examples target with per-example terminate.sh cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 28b9bcc commit 946f86e

16 files changed

Lines changed: 147 additions & 73 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
.idea/*
1212
.vscode/*
13+
.claude/*
1314

1415
cqc/backend/logFile*
1516
examples/**/log/*

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
- **Core code:** `simulaqron/` — simulator source
2222
- **Docs:** `docs/` — Sphinx documentation (currently outdated, being rewritten)
2323

24+
## Git / Commit Policy
25+
- **Never commit or push without explicit user approval.** Always show a summary of staged changes and wait for a clear "go ahead" before running `git commit`.
26+
2427
## Virtual Environment
2528
- The project venv is at `.venv/` — activate with `source .venv/bin/activate`
2629
- Install with `pip install -e .` (skip `[test]` if projectq build fails)

Makefile

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,18 @@ tests_slow:
3636
tests_all:
3737
@${PYTHON} -m pytest -v --capture=tee-sys ${TEST_DIR}
3838

39-
_example_cleanup:
40-
@simulaqron stop 2>/dev/null || true
41-
@sleep 2
42-
4339
examples:
4440
@echo "--- new-sdk examples ---"
4541
@cd examples/new-sdk/corrRNG && timeout 90 bash run.sh
46-
@$(MAKE) _example_cleanup
42+
@cd examples/new-sdk/corrRNG && bash terminate.sh && sleep 3
4743
@cd examples/new-sdk/extendGHZ && timeout 90 bash run.sh
48-
@$(MAKE) _example_cleanup
44+
@cd examples/new-sdk/extendGHZ && bash terminate.sh && sleep 3
4945
@cd examples/new-sdk/teleport && timeout 90 bash run.sh
50-
@$(MAKE) _example_cleanup
51-
@cd examples/new-sdk/classical-client-server && timeout 90 bash run.sh
52-
@$(MAKE) _example_cleanup
53-
@cd examples/new-sdk/template-quantum-local && timeout 90 bash run.sh
54-
@$(MAKE) _example_cleanup
46+
@cd examples/new-sdk/teleport && bash terminate.sh && sleep 3
5547
@cd examples/new-sdk/midCircuitLogic && timeout 90 bash run.sh
56-
@$(MAKE) _example_cleanup
57-
@echo "All new sdk examples passed."
48+
@cd examples/new-sdk/midCircuitLogic && bash terminate.sh && sleep 3
49+
@cd examples/nativeMode/teleport && bash terminate.sh && sleep 3
50+
@echo "Chosen examples passed."
5851

5952
install: test-deps
6053
@$(PYTHON) -m pip install -e . ${PIP_FLAGS}
@@ -83,4 +76,4 @@ _build:
8376

8477
build: _clear_build _build
8578

86-
.PHONY: clean lint python-deps tests tests_slow tests_all examples _example_cleanup ci full_tests verify build
79+
.PHONY: clean lint python-deps tests tests_slow tests_all examples ci full_tests verify build

examples/nativeMode/corrRNG/bobTest.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ def set_virtual_reg(self, qReg):
8989
def remote_test(self):
9090
return "Tested!"
9191

92+
@inlineCallbacks
93+
def _wait_ready(self):
94+
"""Wait until setup_local has connected us to the virtual node."""
95+
while self.virtRoot is None or self.qReg is None:
96+
yield deferLater(reactor, 0.1, lambda: None)
97+
9298
# This can be called by Alice to tell Bob to process the qubit
9399

94100
@inlineCallbacks
@@ -100,17 +106,14 @@ def remote_process_qubit(self, virtualNum):
100106
virtualNum number of the virtual qubit corresponding to the EPR pair received
101107
"""
102108

103-
# Wait until our virtual node connection is ready — Alice may call us
104-
# before setup_local has finished connecting to the virtual node.
105-
while self.virtRoot is None:
106-
yield deferLater(reactor, 0.05, lambda: None)
109+
yield self._wait_ready()
107110

108111
qB = yield self.virtRoot.callRemote("get_virtual_ref", virtualNum)
109112

110113
# Measure
111114
x = yield qB.callRemote("measure")
112115

113-
print("BOB: My Random Number is ", x, "\n")
116+
print("BOB: My Random Number is ", x, "\n", flush=True)
114117

115118
def assemble_qubit(self, realM, imagM):
116119
"""

examples/nativeMode/corrRNG/run.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ if [ ! -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
99
fi
1010
fi
1111

12+
sleep 5
13+
1214
python3 bobTest.py &
13-
sleep 1
1415
python3 aliceTest.py
1516

1617

examples/nativeMode/extendGHZ/bobTest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
from simulaqron.local.setup import setup_local
3333
from simulaqron.general.host_config import SocketsConfig
3434
from simulaqron.settings import network_config
35+
from twisted.internet import reactor
3536
from twisted.internet.defer import inlineCallbacks
37+
from twisted.internet.task import deferLater
3638
from twisted.spread import pb
3739

3840

@@ -85,6 +87,12 @@ def set_virtual_reg(self, qReg):
8587
def remote_test(self):
8688
return "Tested!"
8789

90+
@inlineCallbacks
91+
def _wait_ready(self):
92+
"""Wait until setup_local has connected us to the virtual node."""
93+
while self.virtRoot is None or self.qReg is None:
94+
yield deferLater(reactor, 0.1, lambda: None)
95+
8896
# This can be called by Alice to tell Bob where to get the qubit and what corrections to apply
8997

9098
@inlineCallbacks
@@ -97,6 +105,8 @@ def remote_receive_epr(self, virtualNum):
97105
virtualNum number of the virtual qubit corresponding to the EPR pair received
98106
"""
99107

108+
yield self._wait_ready()
109+
100110
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
101111

102112
# Get a reference to our side of the EPR pair
@@ -115,7 +125,7 @@ def remote_receive_epr(self, virtualNum):
115125

116126
# Measure our qubit
117127
outcome = yield eprB.callRemote("measure")
118-
print(f"Bob's outcome was: {outcome}")
128+
print(f"Bob's outcome was: {outcome}", flush=True)
119129

120130

121131
#####################################################################################################

examples/nativeMode/extendGHZ/charlieTest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def set_virtual_reg(self, qReg):
8686
def remote_test(self):
8787
return "Tested!"
8888

89+
@inlineCallbacks
90+
def _wait_ready(self):
91+
"""Wait until setup_local has connected us to the virtual node."""
92+
while self.virtRoot is None or self.qReg is None:
93+
yield deferLater(reactor, 0.1, lambda: None)
94+
8995
# This can be called by Alice or Bob to tell Charlie where to get the qubit and what to do next
9096

9197
@inlineCallbacks
@@ -97,18 +103,15 @@ def remote_receive_ghz(self, virtualNum):
97103
virtualNum number of the virtual qubit corresponding to the EPR pair received
98104
"""
99105

100-
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
106+
yield self._wait_ready()
101107

102-
# Wait until our virtual node connection is ready — Bob may call us
103-
# before setup_local has finished connecting to the virtual node.
104-
while self.virtRoot is None:
105-
yield deferLater(reactor, 0.05, lambda: None)
108+
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
106109

107110
q = yield self.virtRoot.callRemote("get_virtual_ref", virtualNum)
108111

109112
# Measure it
110113
outcome = yield q.callRemote("measure")
111-
print(f"Charlie's outcome was: {outcome}")
114+
print(f"Charlie's outcome was: {outcome}", flush=True)
112115

113116

114117
#####################################################################################################

examples/nativeMode/extendGHZ/run.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ if [ ! -f ~/.simulaqron_pids/simulaqron_network_default.pid ]; then
99
fi
1010
fi
1111

12+
sleep 5
13+
1214
python3 bobTest.py &
13-
sleep 1
1415
python3 charlieTest.py &
15-
sleep 1
1616
python3 aliceTest.py
1717

1818

examples/nativeMode/graphState/bobTest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,21 @@ def set_virtual_reg(self, qReg):
8888
def remote_test(self):
8989
return "Tested!"
9090

91+
@inlineCallbacks
92+
def _wait_ready(self):
93+
"""Wait until setup_local has connected us to the virtual node."""
94+
while self.virtRoot is None or self.qReg is None:
95+
yield deferLater(reactor, 0.1, lambda: None)
96+
9197
# This can be called by Alice (or other clients on the classical network) to inform Bob
9298
# of an event.
9399

94100
@inlineCallbacks
95101
def remote_receive_qubit(self, virtualNum):
96102

97-
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
103+
yield self._wait_ready()
98104

99-
# Wait until our virtual node connection is ready — Alice may call us
100-
# before setup_local has finished connecting to the virtual node.
101-
while self.virtRoot is None:
102-
yield deferLater(reactor, 0.05, lambda: None)
105+
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
103106

104107
# Get ref of qubit
105108
qB = yield self.virtRoot.callRemote("get_virtual_ref", virtualNum)
@@ -125,7 +128,7 @@ def remote_receive_qubit(self, virtualNum):
125128

126129
# Measure qubit (Z-basis)
127130
outcome = yield qB.callRemote("measure")
128-
print("Bob outcome was:", outcome)
131+
print("Bob outcome was:", outcome, flush=True)
129132

130133

131134
#####################################################################################################

examples/nativeMode/graphState/charlieTest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,24 @@ def set_virtual_reg(self, qReg):
8989
def remote_test(self):
9090
return "Tested!"
9191

92+
@inlineCallbacks
93+
def _wait_ready(self):
94+
"""Wait until setup_local has connected us to the virtual node."""
95+
while self.virtRoot is None or self.qReg is None:
96+
yield deferLater(reactor, 0.1, lambda: None)
97+
9298
# This can be called by Alice (or other clients on the classical network) to inform Bob
9399
# of an event.
94100

95101
@inlineCallbacks
96102
def remote_receive_qubit(self, virtualNum, sender):
97103

104+
yield self._wait_ready()
105+
98106
if sender == "Bob":
99107

100108
logging.debug("LOCAL %s: Getting reference to qubit number %d.", self.node.name, virtualNum)
101109

102-
# Wait until our virtual node connection is ready — Bob may call us
103-
# before setup_local has finished connecting to the virtual node.
104-
while self.virtRoot is None:
105-
yield deferLater(reactor, 0.05, lambda: None)
106-
107110
# Get ref of qubit
108111
self.qC = yield self.virtRoot.callRemote("get_virtual_ref", virtualNum)
109112
qC = self.qC
@@ -135,7 +138,7 @@ def remote_receive_qubit(self, virtualNum, sender):
135138
# Measure qubit (X-basis)
136139
yield qC.callRemote("apply_H")
137140
outcome = yield qC.callRemote("measure")
138-
print("Charlie outcome was:", outcome)
141+
print("Charlie outcome was:", outcome, flush=True)
139142

140143
elif sender == "David":
141144

0 commit comments

Comments
 (0)