Skip to content

Commit

Permalink
cypress: cleanup nasty waitInDevMode (#380)
Browse files Browse the repository at this point in the history
aborted requests when component is unmounting
which usually happens in cypress tests
since we click around faster than the server responds
  • Loading branch information
eh-am authored Sep 6, 2021
1 parent ce5fb76 commit c831911
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 24 deletions.
21 changes: 0 additions & 21 deletions cypress/integration/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,13 @@ describe('basic test', () => {
it('internal sidebar links work', () => {
cy.visit('/')

waitInDevMode(100);

cy.findByTestId('sidebar-comparison').click();
waitInDevMode(100);
cy.location('pathname').should('eq', '/comparison');

cy.findByTestId('sidebar-comparison-diff').click();
waitInDevMode(100);
cy.location('pathname').should('eq', '/comparison-diff');

cy.findByTestId('sidebar-root').click();
waitInDevMode(100);
cy.location('pathname').should('eq', '/');
});
})

// very nasty, just to avoid dealing with the following error
// which requires aborting fetch call and whatnot
// react-dom.development.js:21 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
// in FlameGraphRenderer (created by Context.Consumer)
// in e (created by ConnectFunction)
// in ConnectFunction (created by PyroscopeApp)
// in div (created by PyroscopeApp)
// in div (created by PyroscopeApp)
// in PyroscopeApp (created by ConnectFunction)
// in ConnectFunction
function waitInDevMode(t: number) {
if (!process.env.CI) {
cy.wait(t);
}
}
2 changes: 2 additions & 0 deletions webapp/javascript/components/ComparisonApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ function ComparisonApp(props) {
if (prevPropsRef.renderURL !== renderURL) {
actions.fetchTimeline(renderURL);
}

return actions.abortTimelineRequest;
}, [renderURL]);

return (
Expand Down
1 change: 1 addition & 0 deletions webapp/javascript/components/ComparisonDiffApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function ComparisonDiffApp(props) {
if (prevPropsRef.diffRenderURL !== diffRenderURL) {
actions.fetchTimeline(diffRenderURL);
}
return actions.abortTimelineRequest;
}, [diffRenderURL]);

return (
Expand Down
17 changes: 16 additions & 1 deletion webapp/javascript/components/FlameGraphRenderer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import ProfilerHeader from "./ProfilerHeader";
import { deltaDiffWrapper, parseFlamebearerFormat } from "../util/flamebearer";

import ExportData from "./ExportData";
import {isAbortError} from "../util/abort";


const PX_PER_LEVEL = 18;
Expand Down Expand Up @@ -143,10 +144,18 @@ class FlameGraphRenderer extends React.Component {
}
}

fetchFlameBearerData(url) {
componentWillUnmount() {
this.abortCurrentJSONController();
}

abortCurrentJSONController() {
if (this.currentJSONController) {
this.currentJSONController.abort();
}
}

fetchFlameBearerData(url) {
this.abortCurrentJSONController();
this.currentJSONController = new AbortController();

fetch(`${url}&format=json`, { signal: this.currentJSONController.signal })
Expand All @@ -161,6 +170,12 @@ class FlameGraphRenderer extends React.Component {
this.updateData();
})
})
.catch(e => {
// AbortErrors are fine
if (!isAbortError(e)) {
throw e;
}
})
.finally();
}

Expand Down
5 changes: 4 additions & 1 deletion webapp/javascript/components/PyroscopeApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import TimelineChartWrapper from "./TimelineChartWrapper";
import Header from "./Header";
import Footer from "./Footer";
import { buildRenderURL } from "../util/updateRequests";
import { fetchNames, fetchTimeline } from "../redux/actions";
import { fetchNames, fetchTimeline, abortTimelineRequest } from "../redux/actions";

function PyroscopeApp(props) {
const { actions, renderURL } = props;
Expand All @@ -18,6 +18,8 @@ function PyroscopeApp(props) {
if (prevPropsRef.renderURL !== renderURL) {
actions.fetchTimeline(renderURL);
}

return actions.abortTimelineRequest;
}, [renderURL]);

return (
Expand All @@ -42,6 +44,7 @@ const mapDispatchToProps = (dispatch) => ({
{
fetchTimeline,
fetchNames,
abortTimelineRequest,
},
dispatch
),
Expand Down
9 changes: 8 additions & 1 deletion webapp/javascript/components/TagsBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { connect } from "react-redux";
import "react-dom";
import { Menu, SubMenu, MenuItem, MenuButton } from "@szhsin/react-menu";

import { fetchTags, fetchTagValues, setQuery } from "../redux/actions";
import { fetchTags, fetchTagValues, setQuery, abortFetchTags, abortFetchTagValues } from "../redux/actions";
import "../util/prism";

function TagsBar({ query, actions, tags, tagValuesLoading }) {
Expand All @@ -16,6 +16,8 @@ function TagsBar({ query, actions, tags, tagValuesLoading }) {

useEffect(() => {
actions.fetchTags(query);

return actions.abortFetchTags;
}, [query]);

const submitTagsValue = (newValue) => {
Expand All @@ -40,6 +42,10 @@ function TagsBar({ query, actions, tags, tagValuesLoading }) {
const loadTagValues = (tag) => {
actions.fetchTagValues(query, tag);
};
useEffect(() => {
// since fetchTagValues may be running
return actions.abortFetchTagValues;
}, [])

const onTagsValueChange = (tagKey, tagValue) => {
let newQuery;
Expand Down Expand Up @@ -135,6 +141,7 @@ const mapDispatchToProps = (dispatch) => ({
{
fetchTags,
fetchTagValues,
abortFetchTags,
setQuery,
},
dispatch
Expand Down
73 changes: 73 additions & 0 deletions webapp/javascript/redux/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
SET_RIGHT_FROM,
SET_RIGHT_UNTIL,
} from "./actionTypes";
import { isAbortError } from "../util/abort";

export const setDateRange = (from, until) => ({
type: SET_DATE_RANGE,
Expand Down Expand Up @@ -103,7 +104,15 @@ export const setQuery = (query) => ({
payload: { query },
});

/**
* ATTENTION! There may be race conditions:
* Since a new controller is created every time a 'fetch' action is called
* A badly timed 'abort' action may cancel the brand new 'fetch' action!
*/
let currentTimelineController;
let fetchTagController;
let fetchTagValuesController;

export function fetchTimeline(url) {
return (dispatch) => {
if (currentTimelineController) {
Expand All @@ -118,24 +127,62 @@ export function fetchTimeline(url) {
.then((data) => {
dispatch(receiveTimeline(data));
})
.catch((e) => {
// AbortErrors are fine
if (!isAbortError(e)) {
throw e;
}
})
.finally();
};
}

export function abortTimelineRequest() {
return () => {
if (currentTimelineController) {
currentTimelineController.abort();
}
};
}

export function fetchTags(query) {
return (dispatch) => {
if (fetchTagController) {
fetchTagController.abort();
}
fetchTagController = new AbortController();

dispatch(requestTags());
return fetch(`/labels?query=${encodeURIComponent(query)}`)
.then((response) => response.json())
.then((data) => {
dispatch(receiveTags(data));
})
.catch((e) => {
// AbortErrors are fine
if (!isAbortError(e)) {
throw e;
}
})
.finally();
};
}

export function abortFetchTags() {
return () => {
if (fetchTagController) {
fetchTagController.abort();
}
};
}

export function fetchTagValues(query, tag) {
return (dispatch) => {
if (fetchTagValuesController) {
fetchTagValuesController.abort();
}
fetchTagValuesController = new AbortController();

dispatch(requestTagValues(tag));
return fetch(
`/label-values?label=${encodeURIComponent(
Expand All @@ -146,9 +193,22 @@ export function fetchTagValues(query, tag) {
.then((data) => {
dispatch(receiveTagValues(data, tag));
})
.catch((e) => {
// AbortErrors are fine
if (!fetchTagValuesController.signal.aborted) {
throw e;
}
})
.finally();
};
}
export function abortFetchTagValues() {
return () => {
if (fetchTagValuesController) {
fetchTagValuesController.abort();
}
};
}

let currentNamesController;
export function fetchNames() {
Expand All @@ -166,6 +226,19 @@ export function fetchNames() {
.then((data) => {
dispatch(receiveNames(data));
})
.catch((e) => {
// AbortErrors are fine
if (!isAbortError(e)) {
throw e;
}
})
.finally();
};
}
export function abortFetchNames() {
return () => {
if (abortFetchNames) {
abortFetchNames.abort();
}
};
}
9 changes: 9 additions & 0 deletions webapp/javascript/util/abort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function isAbortError(err) {
if (!err) {
return false;
}

// https://developer.mozilla.org/en-US/docs/Web/API/DOMException
return err.name === 'AbortError'
|| error.code === 20;
}

0 comments on commit c831911

Please sign in to comment.