Merge pull request #17 from sgotti/abort_async_fetch_component_destroy

*: abort async fetch on component destroy
This commit is contained in:
Simone Gotti 2019-09-20 10:53:57 +02:00 committed by GitHub
commit c441bae091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 364 additions and 147 deletions

View File

@ -29,6 +29,8 @@ export default {
formatter.use_classes = true;
return {
fetchAbort: null,
items: [],
lastitem: "",
lines: [],
@ -62,7 +64,9 @@ export default {
path += "&follow";
}
let res = await fetch(apiurl(path));
try {
this.fetching = true;
let res = await fetch(apiurl(path), { signal: this.fetchAbort.signal });
if (res.status == 200) {
const reader = res.body.getReader();
@ -71,6 +75,7 @@ export default {
for (;;) {
let { done, value } = await reader.read();
if (done) {
this.fetching = false;
return;
}
@ -109,28 +114,56 @@ export default {
this.lastitem = this.formatter.ansi_to_html(lastline);
}
}
} catch (e) {
// TODO(sgotti) show that log fetching has failed
}
this.fetching = false;
},
abortFetch() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
}
},
watch: {
show: function(post, pre) {
if (pre == false && post == true) {
this.abortFetch();
this.fetch();
}
if (pre == true && post == false) {
this.abortFetch();
}
},
stepphase: function(post, pre) {
if (pre == "notstarted" && post == "running") {
stepphase: function(post) {
if (!this.show) {
return;
}
if (this.fetching) {
return;
}
if (post == "running") {
this.abortFetch();
this.getLogs(true);
} else {
this.abortFetch();
this.getLogs(false);
}
}
},
created: function() {
this.fetchAbort = new AbortController();
if (this.show) {
this.fetch();
}
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
if (this.es !== null) {
this.es.close();
}

View File

@ -59,16 +59,21 @@ export default {
},
data() {
return {
fetchAbort: null,
fetchProjectGroupsLoading: false,
fetchProjectsLoading: false,
projects: [],
projectgroups: [],
polling: null
projectgroups: []
};
},
watch: {
$route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername);
}
@ -101,10 +106,14 @@ export default {
}
this.startFetchProjectsLoading();
let { data, error } = await fetchProjectGroupProjects(
projectgroupref.join("/")
let { data, error, aborted } = await fetchProjectGroupProjects(
projectgroupref.join("/"),
this.fetchAbort.signal
);
this.stopFetchProjectsLoading();
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -117,10 +126,14 @@ export default {
projectgroupref.push(...this.projectgroupref);
}
this.startFetchProjectGroupsLoading();
let { data, error } = await fetchProjectGroupSubgroups(
projectgroupref.join("/")
let { data, error, aborted } = await fetchProjectGroupSubgroups(
projectgroupref.join("/"),
this.fetchAbort.signal
);
this.stopFetchProjectGroupsLoading();
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -131,8 +144,14 @@ export default {
projectGroupLink: projectGroupLink
},
created: function() {
this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername);
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
}
};
</script>

View File

@ -125,13 +125,17 @@ export default {
data() {
return {
now: moment(),
fetchAbort: null,
fetchRunsLoading: false,
fetchRunsLoadingTimeout: false,
fetchRunsError: null,
fetchRunsSchedule: null,
runs: null,
wantedRunsNumber: 25,
hasMoreRuns: false,
polling: null,
project: null,
user: null
};
@ -163,22 +167,32 @@ export default {
return run.tasks_waiting_approval.length > 0;
},
update() {
clearInterval(this.polling);
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunsSchedule);
if (this.projectref !== undefined) {
this.fetchProject();
} else {
this.fetchUser();
}
this.pollData();
},
async fetchProject() {
this.fetchAbort = new AbortController();
let projectref = [
this.ownertype,
this.ownername,
...this.projectref
].join("/");
let { data, error } = await fetchProject(projectref);
let { data, error, aborted } = await fetchProject(
projectref,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -188,7 +202,15 @@ export default {
this.fetchRuns(true);
},
async fetchUser() {
let { data, error } = await fetchUser(this.ownername);
this.fetchAbort = new AbortController();
let { data, error, aborted } = await fetchUser(
this.ownername,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -227,10 +249,20 @@ export default {
if (loading) this.startFetchRunsLoading();
while (!stopFetch) {
let { data, error } = await fetchRuns(group, startRunID, lastrun);
let { data, error, aborted } = await fetchRuns(
group,
startRunID,
lastrun,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.stopFetchRunsLoading();
this.fetchRunsError = error;
this.scheduleFetchRuns();
return;
}
this.fetchRunsError = null;
@ -247,10 +279,12 @@ export default {
this.stopFetchRunsLoading();
this.runs = newRuns;
this.hasMoreRuns = hasMoreRuns;
this.scheduleFetchRuns();
},
pollData() {
clearInterval(this.polling);
this.polling = setInterval(() => {
scheduleFetchRuns() {
clearTimeout(this.fetchRunsSchedule);
this.fetchRunsSchedule = setTimeout(() => {
this.fetchRuns();
}, 2000);
},
@ -293,7 +327,10 @@ export default {
this.update();
},
beforeDestroy() {
clearInterval(this.polling);
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunsSchedule);
}
};
</script>

View File

@ -68,9 +68,11 @@ export default {
},
data() {
return {
fetchAbort: null,
fetchRunError: null,
fetchRunSchedule: null,
run: null,
polling: null,
taskWidth: 200,
taskHeight: 40,
@ -81,6 +83,18 @@ export default {
tasksDisplay: "graph"
};
},
watch: {
$route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
this.fetchAbort = new AbortController();
this.fetchRun();
}
},
methods: {
runTaskLink(task) {
if (this.projectref) {
@ -108,9 +122,17 @@ export default {
return "unknown";
},
async fetchRun() {
let { data, error } = await fetchRun(this.runid);
let { data, error, aborted } = await fetchRun(
this.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.fetchRunError = error;
this.scheduleFetchRun();
return;
}
this.fetchRunError = null;
@ -127,19 +149,25 @@ export default {
taskID
);
}
this.scheduleFetchRun();
},
pollData() {
this.polling = setInterval(() => {
scheduleFetchRun() {
clearTimeout(this.fetchRunSchedule);
this.fetchRunSchedule = setTimeout(() => {
this.fetchRun();
}, 2000);
}
},
created: function() {
this.fetchAbort = new AbortController();
this.fetchRun();
this.pollData();
},
beforeDestroy() {
clearInterval(this.polling);
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
}
};
</script>

View File

@ -64,13 +64,29 @@ export default {
},
data() {
return {
fetchAbort: null,
fetchRunError: null,
fetchTaskError: null,
run: null,
task: null,
polling: null
task: null
};
},
watch: {
$route: async function() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
clearTimeout(this.fetchTaskSchedule);
this.fetchAbort = new AbortController();
this.fetchRun();
this.fetchTask();
}
},
methods: {
taskClass(task) {
if (task.status == "success") return "is-success";
@ -81,38 +97,66 @@ export default {
return "unknown";
},
async fetchRun() {
let { data, error } = await fetchRun(this.runid);
let { data, error, aborted } = await fetchRun(
this.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.fetchRunError = error;
this.scheduleFetchRun();
return;
}
this.fetchRunError = error;
this.run = data;
this.scheduleFetchRun();
},
async fetchTask() {
let { data, error } = await fetchTask(this.runid, this.taskid);
let { data, error, aborted } = await fetchTask(
this.runid,
this.taskid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.fetchTaskError = error;
this.scheduleFetchTask();
return;
}
this.fetchTaskError = error;
this.task = data;
this.scheduleFetchTask();
},
pollData() {
this.polling = setInterval(() => {
this.fetchTask();
scheduleFetchRun() {
clearTimeout(this.fetchRunSchedule);
this.fetchRunSchedule = setTimeout(() => {
this.fetchRun();
}, 2000);
},
scheduleFetchTask() {
clearTimeout(this.fetchTaskSchedule);
this.fetchTaskSchedule = setTimeout(() => {
this.fetchTask();
}, 2000);
},
approveTask: approveTask
},
created: function() {
this.fetchAbort = new AbortController();
this.fetchRun();
this.fetchTask();
this.pollData();
},
beforeDestroy() {
clearInterval(this.polling);
if (this.fetchAbort) {
this.fetchAbort.abort();
}
clearTimeout(this.fetchRunSchedule);
clearTimeout(this.fetchTaskSchedule);
}
};
</script>

View File

@ -71,13 +71,16 @@ export async function registerapi(init) {
}
}
export async function fetch(url, init) {
if (init === undefined) {
export async function fetch(url, init, signal) {
if (!init) {
init = {}
}
if (init.headers === undefined) {
init["headers"] = {}
}
if (signal) {
init["signal"] = signal;
}
let idToken = getIdToken();
if (idToken) {
init.headers["Authorization"] = "bearer " + idToken

View File

@ -1,10 +1,9 @@
import { apiurl, loginapi, registerapi } from "@/util/auth";
import { fetch as authfetch } from "@/util/auth";
import router from "@/router";
import { apiurl, fetch as authfetch, loginapi, registerapi } from "@/util/auth";
export async function fetch(url, init) {
export async function fetch(url, init, signal) {
try {
let res = await authfetch(url, init)
let res = await authfetch(url, init, signal)
if (!res.ok) {
if (res.status === 401) {
router.push({ name: "login" })
@ -31,6 +30,9 @@ export async function fetch(url, init) {
return { data: await res.json(), error: null }
}
} catch (e) {
if (e.name == 'AbortError') {
return { data: null, error: null, aborted: true, }
}
return { data: null, error: "api call failed: " + e.message }
}
}
@ -82,17 +84,17 @@ export async function register(username, remotesourcename, remoteloginname, remo
}
}
export async function fetchCurrentUser() {
export async function fetchCurrentUser(signal) {
let path = "/user"
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchOrgMembers(orgref) {
export async function fetchOrgMembers(orgref, signal) {
let path = "/orgs/" + orgref + "/members"
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchRuns(group, startRunID, lastrun) {
export async function fetchRuns(group, startRunID, lastrun, signal) {
let u = apiurl("/runs");
if (group) {
u.searchParams.append("group", group)
@ -104,49 +106,49 @@ export async function fetchRuns(group, startRunID, lastrun) {
u.searchParams.append("start", startRunID)
}
return await fetch(u)
return await fetch(u, null, signal)
}
export async function fetchRun(runid) {
return await fetch(apiurl("/runs/" + runid));
export async function fetchRun(runid, signal) {
return await fetch(apiurl("/runs/" + runid), null, signal);
}
export async function fetchTask(runid, taskid) {
return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid))
export async function fetchTask(runid, taskid, signal) {
return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid), signal)
}
export async function fetchUser(username) {
export async function fetchUser(username, signal) {
let path = "/users/" + username
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchProjectGroup(projectgroupref) {
export async function fetchProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchProjectGroupSubgroups(projectgroupref) {
export async function fetchProjectGroupSubgroups(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/subgroups";
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchProjectGroupProjects(projectgroupref) {
export async function fetchProjectGroupProjects(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/projects";
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchProject(ref) {
export async function fetchProject(ref, signal) {
let path = "/projects/" + encodeURIComponent(ref)
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchSecrets(ownertype, ref, all) {
export async function fetchSecrets(ownertype, ref, all, signal) {
let path
if (ownertype == "project") {
path = "/projects/"
@ -158,10 +160,10 @@ export async function fetchSecrets(ownertype, ref, all) {
if (all) {
path += "?tree&removeoverridden";
}
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function fetchVariables(ownertype, ref, all) {
export async function fetchVariables(ownertype, ref, all, signal) {
let path
if (ownertype == "project") {
path = "/projects/"
@ -173,10 +175,10 @@ export async function fetchVariables(ownertype, ref, all) {
if (all) {
path += "?tree&removeoverridden";
}
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function createOrganization(orgname, visibility) {
export async function createOrganization(orgname, visibility, signal) {
let path = "/orgs"
let init = {
method: "POST",
@ -185,10 +187,10 @@ export async function createOrganization(orgname, visibility) {
visibility: visibility,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function createUserToken(username, tokenname) {
export async function createUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens"
let init = {
method: "POST",
@ -196,18 +198,18 @@ export async function createUserToken(username, tokenname) {
token_name: tokenname,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function deleteUserToken(username, tokenname) {
export async function deleteUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens/" + tokenname
let init = {
method: "DELETE",
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function createUserLinkedAccount(username, remotesourcename, loginname, password) {
export async function createUserLinkedAccount(username, remotesourcename, loginname, password, signal) {
let path = "/users/" + username + "/linkedaccounts"
let init = {
method: "POST",
@ -217,18 +219,18 @@ export async function createUserLinkedAccount(username, remotesourcename, loginn
remote_source_login_password: password,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function deleteLinkedAccount(username, laid) {
export async function deleteLinkedAccount(username, laid, signal) {
let path = "/users/" + username + "/linkedaccounts/" + laid
let init = {
method: "DELETE",
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function restartRun(runid, fromStart) {
export async function restartRun(runid, fromStart, signal) {
let path = "/runs/" + runid + "/actions"
let init = {
method: "PUT",
@ -237,10 +239,10 @@ export async function restartRun(runid, fromStart) {
from_start: fromStart
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function cancelRun(runid) {
export async function cancelRun(runid, signal) {
let path = "/runs/" + runid + "/actions"
let init = {
method: "PUT",
@ -248,10 +250,10 @@ export async function cancelRun(runid) {
action_type: "cancel"
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function stopRun(runid) {
export async function stopRun(runid, signal) {
let path = "/runs/" + runid + "/actions"
let init = {
method: "PUT",
@ -259,10 +261,10 @@ export async function stopRun(runid) {
action_type: "stop"
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function approveTask(runid, taskid) {
export async function approveTask(runid, taskid, signal) {
let path = "/runs/" + runid + "/tasks/" + taskid + "/actions"
let init = {
method: "PUT",
@ -270,20 +272,20 @@ export async function approveTask(runid, taskid) {
action_type: "approve"
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function fetchRemoteSources() {
export async function fetchRemoteSources(signal) {
let path = "/remotesources"
return await fetch(apiurl(path));
return await fetch(apiurl(path), null, signal);
}
export async function userRemoteRepos(remotesourceid) {
export async function userRemoteRepos(remotesourceid, signal) {
let path = "/user/remoterepos/" + remotesourceid
return await fetch(apiurl(path));
return await fetch(apiurl(path, null, signal));
}
export async function createProjectGroup(parentref, name, visibility) {
export async function createProjectGroup(parentref, name, visibility, signal) {
let path = "/projectgroups"
let init = {
method: "POST",
@ -293,10 +295,10 @@ export async function createProjectGroup(parentref, name, visibility) {
visibility: visibility
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function updateProjectGroup(projectgroupref, name, visibility) {
export async function updateProjectGroup(projectgroupref, name, visibility, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
let init = {
method: "PUT",
@ -305,10 +307,10 @@ export async function updateProjectGroup(projectgroupref, name, visibility) {
visibility: visibility,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function createProject(parentref, name, visibility, remotesourcename, remoterepopath) {
export async function createProject(parentref, name, visibility, remotesourcename, remoterepopath, signal) {
let path = "/projects"
let init = {
method: "POST",
@ -320,10 +322,10 @@ export async function createProject(parentref, name, visibility, remotesourcenam
repo_path: remoterepopath,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function updateProject(projectref, name, visibility) {
export async function updateProject(projectref, name, visibility, signal) {
let path = "/projects/" + encodeURIComponent(projectref)
let init = {
method: "PUT",
@ -332,29 +334,29 @@ export async function updateProject(projectref, name, visibility) {
visibility: visibility,
})
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function deleteProject(projectref) {
export async function deleteProject(projectref, signal) {
let path = "/projects/" + encodeURIComponent(projectref)
let init = {
method: "DELETE",
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function projectUpdateRepoLinkedAccount(projectref) {
export async function projectUpdateRepoLinkedAccount(projectref, signal) {
let path = "/projects/" + encodeURIComponent(projectref) + "/updaterepolinkedaccount"
let init = {
method: "PUT",
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}
export async function deleteProjectGroup(projectgroupref) {
export async function deleteProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
let init = {
method: "DELETE",
}
return await fetch(apiurl(path), init)
return await fetch(apiurl(path), init, signal)
}

View File

@ -24,7 +24,6 @@ export default {
error: null,
run: null,
code: this.$route.query.code,
polling: null,
username: null
};
},

View File

@ -168,15 +168,28 @@ export default {
},
data() {
return {
fetchAbort: null,
dropdownActive: false,
run: null
};
},
watch: {
$route: async function(route) {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.run = null;
if (route.params.runid) {
let { data, error } = await fetchRun(route.params.runid);
let { data, error, aborted } = await fetchRun(
route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -199,14 +212,27 @@ export default {
}
},
created: async function() {
this.fetchAbort = new AbortController();
if (this.$route.params.runid) {
let { data, error } = await fetchRun(this.$route.params.runid);
let { data, error } = await fetchRun(
this.$route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
}
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
}
};
</script>

View File

@ -164,15 +164,28 @@ export default {
},
data() {
return {
fetchAbort: null,
dropdownActive: false,
run: null
};
},
watch: {
$route: async function(route) {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
this.fetchAbort = new AbortController();
this.run = null;
if (route.params.runid) {
let { data, error } = await fetchRun(route.params.runid);
let { data, error, aborted } = await fetchRun(
route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
@ -204,14 +217,27 @@ export default {
}
},
created: async function() {
this.fetchAbort = new AbortController();
if (this.$route.params.runid) {
let { data, error } = await fetchRun(this.$route.params.runid);
let { data, error, aborted } = await fetchRun(
this.$route.params.runid,
this.fetchAbort.signal
);
if (aborted) {
return;
}
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
}
},
beforeDestroy() {
if (this.fetchAbort) {
this.fetchAbort.abort();
}
}
};
</script>