feat(ping): implement scoped request handling and cancellation for ping requests to improve performance and resource management
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m14s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 2m14s
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"sessionId":"378b5f","runId":"baseline","hypothesisId":"H1_H2","location":"frontend/src/lib/api.js:request","message":"API request started","data":{"method":"GET","url":"/alerts"},"timestamp":1771911695277}
|
{"sessionId":"378b5f","runId":"baseline","hypothesisId":"H1_H2","location":"frontend/src/lib/api.js:request","message":"API request started","data":{"method":"GET","url":"/alerts"},"timestamp":1771911955281}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function EasySwitchManager() {
|
|||||||
const [expandedServers, setExpandedServers] = useState(new Set()); // Развернутые серверы
|
const [expandedServers, setExpandedServers] = useState(new Set()); // Развернутые серверы
|
||||||
const [groupByTags, setGroupByTags] = useState(true); // Группировка по тегам
|
const [groupByTags, setGroupByTags] = useState(true); // Группировка по тегам
|
||||||
// Единый источник пингов (кеш + API) — общий с страницей фильтров
|
// Единый источник пингов (кеш + API) — общий с страницей фильтров
|
||||||
const { pingMap, requestPings } = usePing();
|
const { pingMap, requestPings, cancelScope } = usePing();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -115,9 +115,20 @@ function EasySwitchManager() {
|
|||||||
if (gw.ip) pingTasks.push({ routerId, gatewayIp: gw.ip });
|
if (gw.ip) pingTasks.push({ routerId, gatewayIp: gw.ip });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
requestPings(pingTasks);
|
requestPings(pingTasks, { scope: 'easy-switch' });
|
||||||
}, [location.pathname, servers, inventoryServers, requestPings]);
|
}, [location.pathname, servers, inventoryServers, requestPings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (location.pathname === '/easy-switch') return;
|
||||||
|
cancelScope('easy-switch');
|
||||||
|
}, [location.pathname, cancelScope]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cancelScope('easy-switch');
|
||||||
|
};
|
||||||
|
}, [cancelScope]);
|
||||||
|
|
||||||
const loadDataWithParams = async (communities, inventory, ncGatewaysParam, signal) => {
|
const loadDataWithParams = async (communities, inventory, ncGatewaysParam, signal) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
@@ -258,7 +269,7 @@ function EasySwitchManager() {
|
|||||||
if (gw.ip) pingTasks.push({ routerId, gatewayIp: gw.ip });
|
if (gw.ip) pingTasks.push({ routerId, gatewayIp: gw.ip });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
requestPings(pingTasks);
|
requestPings(pingTasks, { scope: 'easy-switch' });
|
||||||
|
|
||||||
// Отладочная информация
|
// Отладочная информация
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const PING_CACHE_TTL_MS = 2 * 60 * 1000; // 2 минуты
|
|||||||
export function PingProvider({ children }) {
|
export function PingProvider({ children }) {
|
||||||
const [pingMap, setPingMap] = useState({});
|
const [pingMap, setPingMap] = useState({});
|
||||||
const inFlightRef = useRef(new Set());
|
const inFlightRef = useRef(new Set());
|
||||||
|
const controllersRef = useRef(new Map()); // key -> AbortController
|
||||||
|
const keyScopesRef = useRef(new Map()); // key -> scope
|
||||||
const pingMapRef = useRef(pingMap);
|
const pingMapRef = useRef(pingMap);
|
||||||
const timestampsRef = useRef({}); // key → Date.now() когда запись установлена
|
const timestampsRef = useRef({}); // key → Date.now() когда запись установлена
|
||||||
pingMapRef.current = pingMap;
|
pingMapRef.current = pingMap;
|
||||||
@@ -44,8 +46,10 @@ export function PingProvider({ children }) {
|
|||||||
* Пропускаются ключи с валидным кешем (не истёк TTL) и уже запрашиваемые.
|
* Пропускаются ключи с валидным кешем (не истёк TTL) и уже запрашиваемые.
|
||||||
* @param tasks Array<{ routerId, gatewayIp }>
|
* @param tasks Array<{ routerId, gatewayIp }>
|
||||||
*/
|
*/
|
||||||
const requestPings = useCallback((tasks) => {
|
const requestPings = useCallback((tasks, options = {}) => {
|
||||||
if (!Array.isArray(tasks) || tasks.length === 0) return;
|
if (!Array.isArray(tasks) || tasks.length === 0) return;
|
||||||
|
const scope = String(options?.scope || 'default');
|
||||||
|
const force = Boolean(options?.force);
|
||||||
|
|
||||||
const toRequest = [];
|
const toRequest = [];
|
||||||
tasks.forEach(({ routerId, gatewayIp }) => {
|
tasks.forEach(({ routerId, gatewayIp }) => {
|
||||||
@@ -53,40 +57,71 @@ export function PingProvider({ children }) {
|
|||||||
if (!routerId || !ip) return;
|
if (!routerId || !ip) return;
|
||||||
const key = getKey(routerId, ip);
|
const key = getKey(routerId, ip);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
if (isCacheValid(key)) return;
|
if (!force && isCacheValid(key)) return;
|
||||||
if (inFlightRef.current.has(key)) return;
|
if (inFlightRef.current.has(key)) return;
|
||||||
inFlightRef.current.add(key);
|
inFlightRef.current.add(key);
|
||||||
toRequest.push({ routerId, ip, key });
|
toRequest.push({ routerId, ip, key });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://192.168.10.2:7343/ingest/aa002dd6-6968-4a35-b09d-f05302d83266',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'378b5f'},body:JSON.stringify({sessionId:'378b5f',runId:'post-fix',hypothesisId:'H6',location:'frontend/src/contexts/PingContext.jsx:requestPings',message:'Ping batch scheduled',data:{scope,count:toRequest.length,force},timestamp:Date.now()})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
toRequest.forEach(({ routerId, ip, key }) => {
|
toRequest.forEach(({ routerId, ip, key }) => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
controllersRef.current.set(key, controller);
|
||||||
|
keyScopesRef.current.set(key, scope);
|
||||||
api
|
api
|
||||||
.post('/mikrotik/ping', {
|
.post('/mikrotik/ping', {
|
||||||
serverId: routerId,
|
serverId: routerId,
|
||||||
gatewayIp: ip,
|
gatewayIp: ip,
|
||||||
count: PING_COUNT,
|
count: PING_COUNT,
|
||||||
})
|
}, { signal: controller.signal })
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
const value = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
const value = typeof data?.avgMs === 'number' ? Math.round(data.avgMs) : null;
|
||||||
timestampsRef.current[key] = Date.now();
|
timestampsRef.current[key] = Date.now();
|
||||||
setPingMap((prev) => ({ ...prev, [key]: value }));
|
setPingMap((prev) => ({ ...prev, [key]: value }));
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
if (e?.code === 'ERR_CANCELED' || e?.name === 'CanceledError' || e?.name === 'AbortError') {
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://192.168.10.2:7343/ingest/aa002dd6-6968-4a35-b09d-f05302d83266',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'378b5f'},body:JSON.stringify({sessionId:'378b5f',runId:'post-fix',hypothesisId:'H6',location:'frontend/src/contexts/PingContext.jsx:ping-canceled',message:'Ping request canceled',data:{routerId:String(routerId||''),gatewayIp:String(ip||''),scope:String(keyScopesRef.current.get(key)||'')},timestamp:Date.now()})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.warn('PingContext: failed to fetch ping for', routerId, ip, e?.message || e);
|
console.warn('PingContext: failed to fetch ping for', routerId, ip, e?.message || e);
|
||||||
timestampsRef.current[key] = Date.now();
|
timestampsRef.current[key] = Date.now();
|
||||||
setPingMap((prev) => ({ ...prev, [key]: null }));
|
setPingMap((prev) => ({ ...prev, [key]: null }));
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
inFlightRef.current.delete(key);
|
inFlightRef.current.delete(key);
|
||||||
|
controllersRef.current.delete(key);
|
||||||
|
keyScopesRef.current.delete(key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [isCacheValid]);
|
}, [isCacheValid]);
|
||||||
|
|
||||||
|
const cancelScope = useCallback((scope) => {
|
||||||
|
const scopeName = String(scope || '');
|
||||||
|
if (!scopeName) return;
|
||||||
|
let canceled = 0;
|
||||||
|
controllersRef.current.forEach((controller, key) => {
|
||||||
|
if (keyScopesRef.current.get(key) === scopeName) {
|
||||||
|
controller.abort();
|
||||||
|
canceled += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #region agent log
|
||||||
|
fetch('http://192.168.10.2:7343/ingest/aa002dd6-6968-4a35-b09d-f05302d83266',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'378b5f'},body:JSON.stringify({sessionId:'378b5f',runId:'post-fix',hypothesisId:'H6',location:'frontend/src/contexts/PingContext.jsx:cancelScope',message:'Canceled ping scope',data:{scope:scopeName,canceled},timestamp:Date.now()})}).catch(()=>{});
|
||||||
|
// #endregion
|
||||||
|
}, []);
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
pingMap,
|
pingMap,
|
||||||
getPing,
|
getPing,
|
||||||
requestPings,
|
requestPings,
|
||||||
getKey,
|
getKey,
|
||||||
|
cancelScope,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user