Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 860deda

Browse files
authored
Merge pull request #349 from bennyz/snmp-fix
snmp: fix async handling
2 parents 4cb91c5 + 0edf18c commit 860deda

1 file changed

Lines changed: 34 additions & 16 deletions

File tree

  • packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp

packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import socket
33
from dataclasses import dataclass, field
44
from enum import Enum, IntEnum
5+
from typing import Any, Dict, Tuple
56

67
from pysnmp.carrier.asyncio.dgram import udp
78
from pysnmp.entity import config, engine
@@ -116,9 +117,7 @@ def _setup_snmp(self):
116117
def client(cls) -> str:
117118
return "jumpstarter_driver_snmp.client.SNMPServerClient"
118119

119-
def _snmp_set(self, state: PowerState):
120-
result = {"success": False, "error": None}
121-
120+
def _create_snmp_callback(self, result: Dict[str, Any], response_received: asyncio.Event):
122121
def callback(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorIndex, varBinds, cbCtx):
123122
self.logger.debug(f"Callback {errorIndication} {errorStatus} {errorIndex} {varBinds}")
124123
if errorIndication:
@@ -135,20 +134,35 @@ def callback(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorI
135134
for oid, val in varBinds:
136135
self.logger.debug(f"{oid.prettyPrint()} = {val.prettyPrint()}")
137136
self.logger.debug(f"SNMP set result: {result}")
137+
response_received.set()
138138

139+
return callback
140+
141+
def _setup_event_loop(self) -> Tuple[asyncio.AbstractEventLoop, bool]:
139142
try:
140-
self.logger.info(f"Sending power {state.name} command to {self.host}")
141-
created_loop = False
143+
loop = asyncio.get_running_loop()
144+
return loop, False
145+
except RuntimeError:
146+
loop = asyncio.new_event_loop()
147+
asyncio.set_event_loop(loop)
148+
return loop, True
149+
150+
async def _run_snmp_dispatcher(self, snmp_engine: engine.SnmpEngine, response_received: asyncio.Event):
151+
snmp_engine.open_dispatcher()
152+
await response_received.wait()
153+
snmp_engine.close_dispatcher()
142154

143-
try:
144-
asyncio.get_running_loop()
145-
except RuntimeError:
146-
loop = asyncio.new_event_loop()
147-
asyncio.set_event_loop(loop)
148-
created_loop = True
155+
def _snmp_set(self, state: PowerState):
156+
result = {"success": False, "error": None}
157+
response_received = asyncio.Event()
158+
loop = None
159+
created_loop = False
149160

161+
try:
162+
self.logger.info(f"Sending power {state.name} command to {self.host}")
163+
loop, created_loop = self._setup_event_loop()
150164
snmp_engine = self._setup_snmp()
151-
165+
callback = self._create_snmp_callback(result, response_received)
152166
cmdgen.SetCommandGenerator().send_varbinds(
153167
snmp_engine,
154168
"my-target",
@@ -158,11 +172,15 @@ def callback(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorI
158172
callback,
159173
)
160174

161-
snmp_engine.open_dispatcher(self.timeout)
162-
snmp_engine.close_dispatcher()
175+
dispatcher_task = loop.create_task(self._run_snmp_dispatcher(snmp_engine, response_received))
176+
try:
177+
loop.run_until_complete(asyncio.wait_for(dispatcher_task, self.timeout))
178+
except asyncio.TimeoutError:
179+
self.logger.warning(f"SNMP operation timed out after {self.timeout} seconds")
180+
result["error"] = "SNMP operation timed out"
163181

164182
if not result["success"]:
165-
raise SNMPError(result["error"])
183+
raise SNMPError(result["error"] or "Unknown SNMP error")
166184

167185
return f"Power {state.name} command sent successfully"
168186

@@ -171,7 +189,7 @@ def callback(snmpEngine, sendRequestHandle, errorIndication, errorStatus, errorI
171189
self.logger.error(error_msg)
172190
raise SNMPError(error_msg) from e
173191
finally:
174-
if created_loop:
192+
if created_loop and loop:
175193
loop.close()
176194

177195
@export

0 commit comments

Comments
 (0)