diff --git a/.env.example b/.env.example index b206bb4..08a1cd7 100644 --- a/.env.example +++ b/.env.example @@ -40,5 +40,9 @@ MAX_QUBITS=32 MAX_SHOTS=100000 # Python Bridge Configuration -PYTHON_TIMEOUT=60000 -PYTHON_MEMORY_LIMIT=2048 \ No newline at end of file +# Timeout for Python commands in milliseconds +# Default: 300000 (5 minutes) +# Increase for complex quantum operations or slower hardware +PYTHON_TIMEOUT=300000 +PYTHON_MEMORY_LIMIT=2048 +``` \ No newline at end of file diff --git a/CONFIGURATION.md b/CONFIGURATION.md index bff715b..6e1e435 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -20,6 +20,7 @@ MCP_SERVER_VERSION=1.0.0 CUDAQ_PYTHON_PATH=/usr/local/bin/python3 CUDAQ_DEFAULT_TARGET=qpp-cpu CUDAQ_LOG_LEVEL=info +PYTHON_TIMEOUT=300000 # GPU Configuration CUDA_VISIBLE_DEVICES=0 @@ -36,6 +37,11 @@ MAX_CONCURRENT_JOBS=10 QUANTUM_CIRCUIT_TIMEOUT=30000 MAX_QUBITS=32 MAX_SHOTS=100000 + +# Python Bridge Timeout (in milliseconds) +# Default: 300000 (5 minutes) +# Increase for complex quantum operations or slower hardware +PYTHON_TIMEOUT=300000 ``` ### Claude Desktop Integration diff --git a/README.md b/README.md index 284906b..52dec1e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,11 @@ CUDAQ_PYTHON_PATH=/usr/local/bin/python3 CUDAQ_DEFAULT_TARGET=qpp-cpu CUDAQ_LOG_LEVEL=info +# Python Bridge Timeout (milliseconds) +# Default: 300000 (5 minutes) +# Increase for complex operations or slower hardware +PYTHON_TIMEOUT=300000 + # GPU Configuration CUDA_VISIBLE_DEVICES=0 CUDAQ_ENABLE_GPU=true @@ -487,13 +492,19 @@ Solution: #### Python Bridge Timeout ```bash -Error: Python command timeout +Error: Python command timeout after Xms Solution: -1. Increase timeout in config -2. Check Python path in .env -3. Ensure CUDA Quantum installation +1. Increase timeout with environment variable: + export PYTHON_TIMEOUT=600000 # 10 minutes in milliseconds +2. Check Python path in .env: + CUDAQ_PYTHON_PATH=/usr/local/bin/python3 +3. Ensure CUDA Quantum is properly installed: + python3 -c "import cudaq; print(cudaq.__version__)" +4. Check for Python process errors in logs ``` +**Note**: The default timeout is 300,000ms (5 minutes). Complex quantum operations or slower hardware may require increasing this value via the `PYTHON_TIMEOUT` environment variable. + ### Debug Mode Enable debug logging: diff --git a/python/cudaq_bridge.py b/python/cudaq_bridge.py index 2e134b7..344ae01 100644 --- a/python/cudaq_bridge.py +++ b/python/cudaq_bridge.py @@ -539,11 +539,18 @@ def main(): if command: result = dispatch_command(command, **data) - response = { - "success": True, - "data": result, - "requestId": request_id - } + + # Check if the result already has success/error structure + if isinstance(result, dict) and ("success" in result or "error" in result): + # Result already has proper structure, just add request ID + response = {**result, "requestId": request_id} + else: + # Wrap result in standard response structure + response = { + "success": True, + "data": result, + "requestId": request_id + } print(f"DEBUG - Command {command} completed successfully", file=sys.stderr, flush=True) else: response = { @@ -552,7 +559,10 @@ def main(): "requestId": request_id } - print(json.dumps(response), flush=True) + # Ensure response is sent on a single line with explicit flush + output = json.dumps(response) + print(output, flush=True) + sys.stdout.flush() # Extra flush to ensure it's sent except Exception as e: print(f"ERROR - Exception in main loop: {str(e)}", file=sys.stderr, flush=True) @@ -563,7 +573,9 @@ def main(): "traceback": traceback.format_exc(), "requestId": request.get("requestId", "") if 'request' in locals() else "" } - print(json.dumps(error_response), flush=True) + output = json.dumps(error_response) + print(output, flush=True) + sys.stdout.flush() # Extra flush to ensure it's sent if __name__ == "__main__": diff --git a/src/bridge/python-bridge.ts b/src/bridge/python-bridge.ts index 438e432..10c6913 100644 --- a/src/bridge/python-bridge.ts +++ b/src/bridge/python-bridge.ts @@ -38,10 +38,15 @@ export class PythonBridge extends EventEmitter { constructor(config: PythonBridgeConfig = {}) { super(); + // Get timeout from environment variable or use default + const defaultTimeout = process.env.PYTHON_TIMEOUT + ? parseInt(process.env.PYTHON_TIMEOUT, 10) + : 300000; // Default 5 minutes + this.config = { pythonPath: config.pythonPath || process.env.CUDAQ_PYTHON_PATH || 'python3', scriptPath: config.scriptPath || path.join(process.cwd(), 'python/cudaq_bridge.py'), - timeout: config.timeout || 60000, + timeout: config.timeout || defaultTimeout, maxMemory: config.maxMemory || 2048 }; @@ -102,14 +107,21 @@ export class PythonBridge extends EventEmitter { // Log stderr messages but don't treat them as fatal errors during initialization // Most Python logging goes to stderr even for non-errors - if (error.includes('WARNING') || error.includes('INFO') || error.includes('DEBUG')) { - this.logger.debug('Python log message:', error); - } else if (error.trim()) { - this.logger.error('Python stderr:', error); - // Only reject during initialization if it's a real error (not a log message) - if (!initialized && !error.includes('INFO') && !error.includes('WARNING') && !error.includes('DEBUG')) { + if (error.includes('DEBUG')) { + this.logger.debug('Python debug:', error.trim()); + } else if (error.includes('WARNING')) { + this.logger.warn('Python warning:', error.trim()); + } else if (error.includes('INFO')) { + this.logger.info('Python info:', error.trim()); + } else if (error.includes('ERROR')) { + this.logger.error('Python error:', error.trim()); + // Only reject during initialization if it's a real error + if (!initialized) { reject(new Error(`Python process error: ${error}`)); } + } else if (error.trim()) { + // Unknown stderr output - log as debug + this.logger.debug('Python stderr:', error.trim()); } }); @@ -147,16 +159,25 @@ export class PythonBridge extends EventEmitter { try { const response = JSON.parse(line); - if (response.request_id) { - const pending = this.requestQueue.get(response.request_id); + // Handle both requestId and request_id for compatibility + const reqId = response.requestId || response.request_id; + + if (reqId) { + const pending = this.requestQueue.get(reqId); if (pending) { + this.logger.debug(`Received response for request ${reqId}`); clearTimeout(pending.timeout); - this.requestQueue.delete(response.request_id); + this.requestQueue.delete(reqId); pending.resolve(response); + } else { + this.logger.warn(`Received response for unknown request ${reqId}`); } } } catch (e) { - // Ignore non-JSON lines + // Ignore non-JSON lines (debug logs, etc.) + if (!line.includes('DEBUG') && !line.includes('INFO') && !line.includes('WARNING')) { + this.logger.debug('Non-JSON output from Python:', line); + } } } } catch (error) { @@ -172,30 +193,37 @@ export class PythonBridge extends EventEmitter { const requestId = Math.random().toString(36).substring(2, 15); const request = { command, data, requestId }; - // Set timeout based on command complexity - let timeoutDuration = 5000; // Default 5s + // Use environment variable timeout for all commands + // This allows users to adjust based on hardware capabilities + const timeoutDuration = this.config.timeout; + + this.logger.debug(`Sending command '${command}' with request ID ${requestId} (timeout: ${timeoutDuration}ms)`); - if (command === 'set_target') { - timeoutDuration = 100000; // 100s for target operations - } else if (['sample', 'observe', 'get_state'].includes(command)) { - timeoutDuration = 150000; // 150s for quantum operations - } else if (['get_platform_info', 'get_available_targets', 'list_kernels'].includes(command)) { - timeoutDuration = 30000; // 30s for simple info operations - } const timeout = setTimeout(() => { this.requestQueue.delete(requestId); - reject(new Error(`Python command timeout: ${command}`)); + this.logger.error(`Command '${command}' timed out after ${timeoutDuration}ms (request ID: ${requestId})`); + reject(new Error(`Python command timeout after ${timeoutDuration}ms: ${command}`)); }, timeoutDuration); // Store request with timeout for cleanup this.requestQueue.set(requestId, { resolve, reject, timeout }); if (!this.pythonProcess) { + clearTimeout(timeout); + this.requestQueue.delete(requestId); reject(new Error('Python process not initialized')); return; } - this.pythonProcess.stdin?.write(JSON.stringify(request) + '\n'); + try { + this.pythonProcess.stdin?.write(JSON.stringify(request) + '\n'); + this.logger.debug(`Command '${command}' sent successfully`); + } catch (error) { + clearTimeout(timeout); + this.requestQueue.delete(requestId); + this.logger.error(`Failed to send command '${command}':`, error); + reject(error); + } }); }