1+ import logging
2+ import os
3+ import sys
4+
15from typing import Optional , Dict , Callable , Generator , Any , List , Type
26
37from netqasm .backend .executor import Executor
1014from simulaqron .netqasm_backend .executioner import VanillaSimulaQronExecutioner
1115from simulaqron .sdk .connection import (NewMessageType , GetQubitStateMessage ,
1216 ReturnQubitStateMessage )
13-
17+ from simulaqron . settings import simulaqron_settings
1418
1519class SubroutineHandler (QNodeController ):
1620 def __init__ (self , factory : "NetQASMFactory" , instr_log_dir : Optional [str ] = None , # noqa: F821
1721 flavour : Optional [Flavour ] = None ):
1822 super ().__init__ (factory .name , instr_log_dir = instr_log_dir , flavour = flavour )
1923
2024 self .factory = factory
25+ self ._logger = logging .getLogger ("QnodeController" )
26+ # logging that the user will later see on the screen
27+ #stdout_file = open(f"/tmp/simulaqron-stdout-stderr-netqasmQ-{os.getpid()}.out.txt", "w")
28+ #sys.stdout = stdout_file
29+ #sys.stderr = stdout_file
30+
31+ # Force configure root logger with a handler, ensure our log output to this file
32+ # will allow us to trace back exactly where it came from in the codebase
33+ logging .basicConfig (
34+ format = "%(asctime)s:%(levelname)s:%(name)s:%(filename)s:%(lineno)d:%(message)s" ,
35+ level = simulaqron_settings .log_level ,
36+ force = True ,
37+ stream = sys .stdout # send logs to the same file
38+ )
2139
2240 # Give a way for the executioner to return messages
2341 self ._executor .add_return_msg_func (self ._return_msg )
@@ -35,11 +53,52 @@ def protocol(self, protocol: Protocol):
3553
3654 @inlineCallbacks
3755 def handle_netqasm_message (self , msg_id : int , msg : Message ):
38- yield from super ().handle_netqasm_message (
56+ """
57+ Handle incoming NetQASM messages by bridging two async models.
58+
59+ NetQASM's executor uses Python generators (yield from) while SimulaQron
60+ uses Twisted deferreds (@inlineCallbacks). This method bridges them by:
61+ 1. Running the parent's generator manually
62+ 2. Detecting whether each yielded item is a Twisted Deferred or a nested generator
63+ 3. For Deferreds: yielding to Twisted's reactor to await completion
64+ 4. For nested generators: consuming them fully and capturing their return value
65+
66+ Without this bridge, nested generator return values (like physical_address
67+ from _instr_qalloc) would be lost, causing None to propagate through the system.
68+ This is also what caused the tests to fail, and probably other random weird things.
69+ """
70+ print (f"DEBUG handle_netqasm_message: msg_id={ msg_id } " , flush = True )
71+ gen = super ().handle_netqasm_message (
3972 msg_id = msg_id ,
4073 msg = msg ,
4174 )
4275
76+ # The following is a bug fix to properly wait for twisted deferreds
77+
78+ try :
79+ result = None
80+ iteration = 0
81+ while True :
82+ iteration = iteration + 1
83+ item = gen .send (result )
84+ if hasattr (item , 'addCallback' ): # Deferred
85+ result = yield item
86+ elif hasattr (item , '__next__' ): # Nested generator - consume it
87+ nested_result = None
88+ try :
89+ while True :
90+ nested_item = item .send (nested_result )
91+ if hasattr (nested_item , 'addCallback' ):
92+ nested_result = yield nested_item
93+ else :
94+ nested_result = None
95+ except StopIteration as e :
96+ result = e .value # Get the return value from the generator
97+ else :
98+ result = None
99+ except StopIteration :
100+ pass
101+
43102 def _handle_get_qubit_state (self , get_quibit_state_msg : GetQubitStateMessage ) -> Generator [Any , None , None ]:
44103 assert isinstance (self ._executor , VanillaSimulaQronExecutioner )
45104 casted_executor : VanillaSimulaQronExecutioner = self ._executor
0 commit comments