Skip to content

Commit 615edff

Browse files
committed
fix(agent,teacher): serialize IPv4 snapshot, heartbeat non-blocking refresh, extend TCP read timeout
1 parent 8617060 commit 615edff

3 files changed

Lines changed: 97 additions & 69 deletions

File tree

agent/main.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,10 @@ def _heartbeat_loop(self) -> None:
456456
"type": MSG_HEARTBEAT,
457457
"hostname": socket.gethostname(),
458458
"ipv4": _guess_report_ipv4(),
459-
"ipv4_detail": get_default_ipv4_detail_snapshot(),
459+
"ipv4_detail": get_default_ipv4_detail_snapshot(
460+
ttl_sec=max(90.0, self.heartbeat_sec * 8.0),
461+
recompute_blocking=False,
462+
),
460463
}
461464
)
462465
except (ConnectionError, OSError, ValueError):

agent/network_config.py

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import socket
88
import subprocess
99
import sys
10+
import threading
1011
import time
1112
from typing import Any, Dict, List, Optional, Set, Tuple
1213

@@ -1145,26 +1146,11 @@ def _merge_ipv4_detail_fields(base: Dict[str, Any], add: Dict[str, Any]) -> None
11451146

11461147
_detail_mono = 0.0
11471148
_detail_payload: Dict[str, Any] = {}
1149+
_detail_snap_lock = threading.Lock()
11481150

11491151

1150-
def get_default_ipv4_detail_snapshot(ttl_sec: float = 20.0) -> Dict[str, Any]:
1151-
"""
1152-
默认出口网卡的当前 IPv4 参数,供教师端静态表单预填。
1153-
优先用 PowerShell(默认路由 InterfaceIndex,与网卡显示名无关),再用 WMI 补缺;带短时缓存。
1154-
"""
1155-
global _detail_mono, _detail_payload
1156-
now = time.monotonic()
1157-
if _detail_payload and (now - _detail_mono) < ttl_sec:
1158-
return dict(_detail_payload)
1159-
1160-
if sys.platform != "win32":
1161-
_detail_mono = now
1162-
_detail_payload = {}
1163-
return {}
1164-
1165-
pref = _outbound_ipv4_hint().strip()
1166-
1167-
out: Dict[str, Any] = {
1152+
def _empty_ipv4_detail_for_cache() -> Dict[str, Any]:
1153+
return {
11681154
"ip": "",
11691155
"mask": "",
11701156
"gateway": "",
@@ -1173,54 +1159,92 @@ def get_default_ipv4_detail_snapshot(ttl_sec: float = 20.0) -> Dict[str, Any]:
11731159
"dhcp": False,
11741160
}
11751161

1176-
psd = _powershell_ipv4_detail_by_default_route()
1177-
if psd:
1178-
out.update(psd)
1179-
1180-
widx = _wmic_default_route_interface_index()
1181-
if widx is not None:
1182-
row = _wmic_nic_configuration_by_interface_index(widx)
1183-
if row:
1184-
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row, pref))
1185-
1186-
iface_name, _diag = get_default_ipv4_interface_name()
1187-
if iface_name:
1188-
if_idx = _wmic_interface_index_for_netconnection_id(iface_name)
1189-
if if_idx is not None and (widx is None or if_idx != widx):
1190-
row2 = _wmic_nic_configuration_by_interface_index(if_idx)
1191-
if row2:
1192-
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row2, pref))
1193-
1194-
if not str(out.get("ip") or "").strip() and pref:
1195-
out["ip"] = pref
1196-
1197-
hip = str(out.get("ip") or pref).strip()
1198-
if hip:
1199-
need_more = (
1162+
1163+
def get_default_ipv4_detail_snapshot(
1164+
ttl_sec: float = 75.0,
1165+
*,
1166+
recompute_blocking: bool = True,
1167+
) -> Dict[str, Any]:
1168+
"""
1169+
默认出口网卡的当前 IPv4 参数,供教师端静态表单预填。
1170+
优先用 PowerShell(默认路由 InterfaceIndex,与网卡显示名无关),再用 WMI 补缺;带缓存。
1171+
1172+
recompute_blocking=False:供心跳线程使用;若有其它线程正在全量采集,则立刻返回上一份缓存,
1173+
避免与「按需查询」(ttl_sec=0) 并行叠加两次 WMI/子进程拖过教师端单次读超时。
1174+
"""
1175+
global _detail_mono, _detail_payload
1176+
now = time.monotonic()
1177+
if _detail_payload and (now - _detail_mono) < ttl_sec:
1178+
return dict(_detail_payload)
1179+
1180+
if recompute_blocking:
1181+
_detail_snap_lock.acquire()
1182+
elif not _detail_snap_lock.acquire(blocking=False):
1183+
return dict(_detail_payload) if _detail_payload else _empty_ipv4_detail_for_cache()
1184+
try:
1185+
now2 = time.monotonic()
1186+
if _detail_payload and (now2 - _detail_mono) < ttl_sec:
1187+
return dict(_detail_payload)
1188+
1189+
if sys.platform != "win32":
1190+
_detail_mono = now2
1191+
_detail_payload = {}
1192+
return {}
1193+
1194+
pref = _outbound_ipv4_hint().strip()
1195+
1196+
out: Dict[str, Any] = _empty_ipv4_detail_for_cache()
1197+
1198+
psd = _powershell_ipv4_detail_by_default_route()
1199+
if psd:
1200+
out.update(psd)
1201+
1202+
widx = _wmic_default_route_interface_index()
1203+
if widx is not None:
1204+
row = _wmic_nic_configuration_by_interface_index(widx)
1205+
if row:
1206+
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row, pref))
1207+
1208+
iface_name, _diag = get_default_ipv4_interface_name()
1209+
if iface_name:
1210+
if_idx = _wmic_interface_index_for_netconnection_id(iface_name)
1211+
if if_idx is not None and (widx is None or if_idx != widx):
1212+
row2 = _wmic_nic_configuration_by_interface_index(if_idx)
1213+
if row2:
1214+
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row2, pref))
1215+
1216+
if not str(out.get("ip") or "").strip() and pref:
1217+
out["ip"] = pref
1218+
1219+
hip = str(out.get("ip") or pref).strip()
1220+
if hip:
1221+
need_more = (
1222+
not str(out.get("mask") or "").strip()
1223+
or not str(out.get("gateway") or "").strip()
1224+
or not str(out.get("dns_primary") or "").strip()
1225+
)
1226+
if need_more:
1227+
row_fix = _wmic_nic_configuration_row_for_ipv4(hip)
1228+
if row_fix:
1229+
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row_fix, hip))
1230+
if not str(out.get("gateway") or "").strip():
1231+
gw_rp = _route_print_ipv4_default_gateway()
1232+
if gw_rp:
1233+
out["gateway"] = gw_rp
1234+
1235+
idx_netsh = widx
1236+
if idx_netsh is None and iface_name:
1237+
alt_i = _wmic_interface_index_for_netconnection_id(iface_name)
1238+
if alt_i is not None:
1239+
idx_netsh = alt_i
1240+
if idx_netsh is not None and (
12001241
not str(out.get("mask") or "").strip()
1201-
or not str(out.get("gateway") or "").strip()
12021242
or not str(out.get("dns_primary") or "").strip()
1203-
)
1204-
if need_more:
1205-
row_fix = _wmic_nic_configuration_row_for_ipv4(hip)
1206-
if row_fix:
1207-
_merge_ipv4_detail_fields(out, _detail_row_to_dict(row_fix, hip))
1208-
if not str(out.get("gateway") or "").strip():
1209-
gw_rp = _route_print_ipv4_default_gateway()
1210-
if gw_rp:
1211-
out["gateway"] = gw_rp
1212-
1213-
idx_netsh = widx
1214-
if idx_netsh is None and iface_name:
1215-
alt_i = _wmic_interface_index_for_netconnection_id(iface_name)
1216-
if alt_i is not None:
1217-
idx_netsh = alt_i
1218-
if idx_netsh is not None and (
1219-
not str(out.get("mask") or "").strip()
1220-
or not str(out.get("dns_primary") or "").strip()
1221-
):
1222-
_merge_ipv4_detail_fields(out, _netsh_ipv4_enrich_from_ifindex(idx_netsh))
1223-
1224-
_detail_mono = now
1225-
_detail_payload = dict(out)
1226-
return dict(out)
1243+
):
1244+
_merge_ipv4_detail_fields(out, _netsh_ipv4_enrich_from_ifindex(idx_netsh))
1245+
1246+
_detail_mono = time.monotonic()
1247+
_detail_payload = dict(out)
1248+
return dict(out)
1249+
finally:
1250+
_detail_snap_lock.release()

teacher/server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def _accept_loop(self) -> None:
185185
def _client_loop(self, conn: socket.socket, peer_ip: str) -> None:
186186
session_id: Optional[str] = None
187187
try:
188-
conn.settimeout(120.0)
188+
# Agent 并行采集 IPv4 时单次心跳帧可能延后;读超时过小会在 Win10 上误杀仍为活的连接。
189+
conn.settimeout(300.0)
189190
first = read_frame_from_socket(conn)
190191
if get_message_type(first) != MSG_REGISTER:
191192
write_frame_to_socket(

0 commit comments

Comments
 (0)