Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
fix: fix gradual memory leak / perf issue with cytoscape (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylarmb authored Sep 27, 2024
1 parent df2a6f7 commit 703417d
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 38 deletions.
9 changes: 1 addition & 8 deletions kontrol-frontend/src/contexts/ApiContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export interface ApiContextType {
getFlows: () => Promise<Flow[]>;
getTemplates: () => Promise<void>;
getTopology: () => Promise<components["schemas"]["ClusterTopology"]>;
loading: boolean;
postFlowCreate: (
b: RequestBody<"/tenant/{uuid}/flow/create", "post">,
) => Promise<Flow>;
Expand All @@ -50,7 +49,6 @@ const defaultContextValue: ApiContextType = {
getTopology: async () => {
return { nodes: [], edges: [] };
},
loading: false,
postFlowCreate: async () => ({
"flow-id": "",
"access-entry": [],
Expand All @@ -72,15 +70,13 @@ export const ApiContextProvider = ({ children }: PropsWithChildren) => {
);
const uuid = match?.params.uuid;

const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [templates, setTemplates] = useState<Template[]>([]);

// boilerplate loading state, error handling for any API call
// boilerplate error handling for any API call
const handleApiCall = useCallback(async function <T>(
pendingRequest: Promise<{ data?: T }>, // Api fetch promise
): Promise<T> {
setLoading(true);
try {
const response = await pendingRequest;
if (response.data == null) {
Expand All @@ -91,8 +87,6 @@ export const ApiContextProvider = ({ children }: PropsWithChildren) => {
console.error("Failed to fetch route:", error);
setError((error as Error).message);
throw error;
} finally {
setLoading(false);
}
}, []);

Expand Down Expand Up @@ -198,7 +192,6 @@ export const ApiContextProvider = ({ children }: PropsWithChildren) => {
getFlows,
getTemplates,
getTopology,
loading,
postFlowCreate,
postTemplateCreate,
templates,
Expand Down
68 changes: 38 additions & 30 deletions kontrol-frontend/src/pages/TrafficConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,48 @@ const Page = () => {
const prevResponse = useRef<string>();
const { getTopology } = useApi();
const { refetchFlows, flowVisibility } = useFlowsContext();
const timerRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
const fetchElems = async () => {
const response = await getTopology();
const filtered = {
...response,
nodes: response.nodes.map((node) => {
return {
...node,
versions: node.versions?.filter((version) => {
return flowVisibility[version.flowId] === true;
}),
};
}),
};
const newElems = utils.normalizeData(filtered);

// dont update react state if the API response is identical to the previous one
// This avoids unnecessary re-renders
if (JSON.stringify(newElems) === prevResponse.current) {
return;
}
prevResponse.current = JSON.stringify(newElems);
setElems(newElems);
// re-fetch flows if topology changes
refetchFlows();
const fetchElems = async () => {
const response = await getTopology();
const filtered = {
...response,
nodes: response.nodes.map((node) => {
return {
...node,
versions: node.versions?.filter((version) => {
return flowVisibility[version.flowId] === true;
}),
};
}),
};
const newElems = utils.normalizeData(filtered);

// dont update react state if the API response is identical to the previous one
// This avoids unnecessary re-renders
if (JSON.stringify(newElems) === prevResponse.current) {
return;
}
prevResponse.current = JSON.stringify(newElems);
setElems(newElems);
// re-fetch flows if topology changes
refetchFlows();
};

const startPolling = () => {
timerRef.current = setInterval(fetchElems, pollingIntervalSeconds * 1000);
};

// Continuously fetch elements
const intervalId = setInterval(fetchElems, pollingIntervalSeconds * 1000);
fetchElems();
return () => clearInterval(intervalId);
const stopPolling = () => {
clearInterval(timerRef.current!);
timerRef.current = null;
};

useEffect(() => {
startPolling();
return stopPolling;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [flowVisibility]);
}, []);

return (
<Grid
Expand Down

0 comments on commit 703417d

Please sign in to comment.