Skip to content

Commit f562418

Browse files
authored
Clear intervals & timeouts on unmount (#4452)
* Clear intervals & timeouts on unmount * Cleanup timeouts from contracts & load deployment
1 parent 21f0e36 commit f562418

File tree

8 files changed

+105
-32
lines changed

8 files changed

+105
-32
lines changed

packages/playground/src/components/TfNavigationLoader.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</template>
1414

1515
<script lang="ts">
16-
import { computed, ref, type StyleValue, watch } from "vue";
16+
import { computed, onUnmounted, ref, type StyleValue, watch } from "vue";
1717
1818
import { useNavigationStatus, useOnline } from "../hooks";
1919
@@ -72,6 +72,10 @@ export default {
7272
};
7373
});
7474
75+
onUnmounted(() => {
76+
clear();
77+
});
78+
7579
return { loadingValue, style };
7680
},
7781
};

packages/playground/src/components/TfOfflineNotifier.vue

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@
1010
</VCardText>
1111

1212
<VCardActions v-if="failed" class="d-flex justify-center mb-4">
13-
<VBtn prepend-icon="mdi-reload" variant="outlined" color="secondary" @click="reload">
14-
Reload Now
15-
</VBtn>
13+
<VBtn prepend-icon="mdi-reload" variant="outlined" color="secondary" @click="reload"> Reload Now </VBtn>
1614
</VCardActions>
1715
</VCard>
1816
</VDialog>
1917
</template>
2018

2119
<script lang="ts">
22-
import { computed, ref, watch } from "vue";
20+
import { computed, onUnmounted, ref, watch } from "vue";
2321
2422
import { useOffline } from "../hooks";
2523
@@ -81,6 +79,13 @@ export default {
8179
can reload instantly.`;
8280
});
8381
82+
onUnmounted(() => {
83+
if (interval) {
84+
clearInterval(interval);
85+
interval = null;
86+
}
87+
});
88+
8489
return { offline, failed, reload, dotCount, title, description };
8590
},
8691
};

packages/playground/src/components/contracts_list/contracts_table.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ import { ContractStates, type GridClient, type OverdueDetails } from "@threefold
321321
import { type Contract, ContractState, type NodeStatus } from "@threefold/gridproxy_client";
322322
import { TFChainError } from "@threefold/tfchain_client";
323323
import { DeploymentKeyDeletionError } from "@threefold/types";
324-
import { capitalize, computed, defineComponent, type PropType, type Ref, ref, watch } from "vue";
324+
import { capitalize, computed, defineComponent, onUnmounted, type PropType, type Ref, ref, watch } from "vue";
325325
326326
import { useProfileManagerController } from "@/components/profile_manager_controller.vue";
327327
import type { VDataTableHeader } from "@/types";
@@ -423,6 +423,7 @@ const freeBalance = computed(() => balance.value?.free ?? 0);
423423
const unlockContractLoading = ref(false);
424424
const unlockDialog = ref(false);
425425
const rentContracts = ref<{ [key: number]: number }>({}); // to store the node id with its rent contract
426+
const timeouts: ReturnType<typeof setTimeout>[] = [];
426427
const selectedLockedContracts = computed(() => {
427428
if (selectedContracts.value.length == 0) return false;
428429
for (const contract of selectedContracts.value) {
@@ -615,7 +616,8 @@ async function unlockContract(contractId: number[]) {
615616
`Your request to unlock contract ${contractId} has been processed successfully. Changes may take a few minutes to reflect`,
616617
ToastType.info,
617618
);
618-
setTimeout(() => emits("update:unlock-contracts"), 30000);
619+
const timeoutId = setTimeout(() => emits("update:unlock-contracts"), 30000);
620+
timeouts.push(timeoutId);
619621
contractStateDialog.value = false;
620622
unlockDialog.value = false;
621623
selectedContracts.value = [];
@@ -629,6 +631,12 @@ async function unlockContract(contractId: number[]) {
629631
watch(contractStateDialog, contractStateDialog => {
630632
if (!contractStateDialog) selectedItem.value = undefined;
631633
});
634+
635+
onUnmounted(() => {
636+
timeouts.forEach(timeout => clearTimeout(timeout));
637+
timeouts.length = 0;
638+
});
639+
632640
defineExpose({
633641
reset() {
634642
rentContracts.value = {};

packages/playground/src/components/deposit_dialog.vue

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
@update:model-value="closeDialog"
88
>
99
<v-card>
10-
<VCardTitle class="bg-primary">
11-
Deposit TFT
12-
</VCardTitle>
10+
<VCardTitle class="bg-primary"> Deposit TFT </VCardTitle>
1311
<v-card-text>
1412
<v-container>
1513
<v-row class="py-2">
@@ -20,9 +18,7 @@
2018
{{ selectedName ? selectedName.charAt(0).toUpperCase() + selectedName.slice(1) : "" }}
2119
transaction.
2220
</p>
23-
<p class="mt-1 mb-8 text-secondary text-sm-subtitle-2 font-weight-bold">
24-
Deposit fee is 1 TFT
25-
</p>
21+
<p class="mt-1 mb-8 text-secondary text-sm-subtitle-2 font-weight-bold">Deposit fee is 1 TFT</p>
2622
</div>
2723
<input-tooltip
2824
v-if="selectedName == 'stellar'"
@@ -53,19 +49,15 @@
5349
<v-col>
5450
<QRPlayStore :qr="qrCodeText">
5551
<b> OR </b>
56-
<p class="mb-3">
57-
Use ThreeFold Connect to scan this QRcode:
58-
</p>
52+
<p class="mb-3">Use ThreeFold Connect to scan this QRcode:</p>
5953
</QRPlayStore>
6054
</v-col>
6155
</v-row>
6256
</v-container>
6357
<v-divider />
6458
</v-card-text>
6559
<v-card-actions class="justify-end my-1 mr-2">
66-
<v-btn color="anchor" @click="closeDialog">
67-
Close
68-
</v-btn>
60+
<v-btn color="anchor" @click="closeDialog"> Close </v-btn>
6961
<v-btn color="secondary" :href="manual.tft_bridges" target="_blank" text="Learn more?" />
7062
</v-card-actions>
7163
</v-card>
@@ -157,6 +149,10 @@ const closeDialog = () => {
157149
158150
onBeforeUnmount(() => {
159151
destroyed = true;
152+
if (interval.value !== null) {
153+
window.clearInterval(interval.value);
154+
interval.value = null;
155+
}
160156
});
161157
</script>
162158
<script lang="ts">

packages/playground/src/dashboard/components/node_details.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484

8585
<script setup lang="ts">
8686
import type { GridNode } from "@threefold/gridproxy_client";
87-
import { onMounted, type PropType, ref } from "vue";
87+
import { onMounted, onUnmounted, type PropType, ref } from "vue";
8888
8989
import { gridProxyClient } from "@/clients";
9090
import type { NodeDetailsCard } from "@/types";
@@ -202,14 +202,20 @@ function loadingDots() {
202202
dots.value += ".";
203203
}
204204
}
205+
206+
onUnmounted(() => {
207+
if (interval.value !== null) {
208+
window.clearInterval(interval.value);
209+
interval.value = null;
210+
}
211+
});
205212
</script>
206213

207214
<script lang="ts">
208215
import CardDetails from "@/components/node_details_cards/card_details.vue";
209216
import GPUDetailsCard from "@/components/node_details_cards/gpu_details_card.vue";
210217
211218
export default {
212-
213219
name: "NodeDetails",
214220
components: {
215221
CardDetails,

packages/playground/src/utils/load_deployment.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,36 @@ export async function loadVms(grid: GridClient, options: LoadVMsOptions = {}) {
6969
}
7070
return res;
7171
});
72+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
7273
const timeoutPromise = new Promise((resolve, reject) => {
73-
setTimeout(() => {
74+
timeoutId = setTimeout(() => {
7475
reject(new Error("Timeout"));
7576
}, window.env.TIMEOUT);
7677
});
7778

7879
try {
79-
const result = await Promise.race([machinePromise, timeoutPromise]);
80+
const result = await Promise.race([
81+
machinePromise.finally(() => {
82+
if (timeoutId !== null) {
83+
clearTimeout(timeoutId);
84+
timeoutId = null;
85+
}
86+
}),
87+
timeoutPromise.finally(() => {
88+
timeoutId = null;
89+
}),
90+
]);
8091
if (result instanceof Error && result.message === "Timeout") {
8192
console.error(`Timeout loading deployment with name ${name}`);
8293
return null;
8394
} else {
8495
return result;
8596
}
8697
} catch (e) {
98+
if (timeoutId !== null) {
99+
clearTimeout(timeoutId);
100+
timeoutId = null;
101+
}
87102
console.error(`Failed to load deployment with name ${name}:\n${normalizeError(e, "No errors were provided.")}`);
88103
failedDeployments.push({ name, nodes: nodeIds, contracts: contracts });
89104
}
@@ -188,13 +203,26 @@ export async function loadK8s(grid: GridClient) {
188203

189204
return res;
190205
});
206+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
191207
const timeoutPromise = new Promise((resolve, reject) => {
192-
setTimeout(() => {
208+
timeoutId = setTimeout(() => {
193209
reject(new Error("Timeout"));
194210
}, window.env.TIMEOUT);
195211
});
196212

197-
const result = await Promise.race([clusterPromise, timeoutPromise]);
213+
const result = await Promise.race([
214+
clusterPromise.finally(() => {
215+
// Clear timeout if main promise resolves/rejects first
216+
if (timeoutId !== null) {
217+
clearTimeout(timeoutId);
218+
timeoutId = null;
219+
}
220+
}),
221+
timeoutPromise.finally(() => {
222+
// Clear timeout reference when timeout promise resolves/rejects
223+
timeoutId = null;
224+
}),
225+
]);
198226
if (result instanceof Error && result.message === "Timeout") {
199227
console.error(`Timeout loading deployment with name ${name}`);
200228
return null;

packages/playground/src/weblets/profile_manager.vue

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160
</VDialog>
161161
</template>
162162
<script lang="ts" setup>
163-
import { computed, onMounted, ref, watch } from "vue";
163+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
164164
import { nextTick } from "vue";
165165
import { useTheme } from "vuetify";
166166
@@ -246,7 +246,9 @@ const profileManagerController = useProfileManagerController();
246246
const balance = profileManagerController.balance;
247247
const freeBalance = computed(() => balance.value?.free ?? 0);
248248
const kyc = useKYC();
249-
let interval: any;
249+
let interval: ReturnType<typeof setInterval> | null = null;
250+
const timeouts: ReturnType<typeof setTimeout>[] = [];
251+
250252
watch(
251253
() => profileManager.profile,
252254
profile => {
@@ -258,6 +260,10 @@ watch(
258260
} else {
259261
kyc.clear();
260262
if (interval) clearInterval(interval);
263+
interval = null;
264+
// Clear all pending timeouts
265+
timeouts.forEach(timeout => clearTimeout(timeout));
266+
timeouts.length = 0;
261267
balance.value = undefined;
262268
}
263269
},
@@ -277,6 +283,16 @@ onMounted(async () => {
277283
await mounted();
278284
});
279285
286+
onUnmounted(() => {
287+
if (interval) {
288+
clearInterval(interval);
289+
interval = null;
290+
}
291+
// Clear all pending timeouts
292+
timeouts.forEach(timeout => clearTimeout(timeout));
293+
timeouts.length = 0;
294+
});
295+
280296
async function handleProfileDialog(value: boolean) {
281297
emit("update:modelValue", value);
282298
if (profileManager?.profile && value) __loadBalance(profileManager.profile);
@@ -305,7 +321,8 @@ async function __loadBalance(profile?: Profile, tries = 1) {
305321
return;
306322
}
307323
308-
setTimeout(() => __loadBalance(profile, tries + 1), Math.floor(Math.exp(tries) * 1_000));
324+
const timeoutId = setTimeout(() => __loadBalance(profile, tries + 1), Math.floor(Math.exp(tries) * 1_000));
325+
timeouts.push(timeoutId);
309326
}
310327
}
311328
profileManagerController.set({ loadBalance: __loadBalance });

packages/playground/src/weblets/tf_contracts_list.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@
258258
import type { ContractsOverdue, GridClient } from "@threefold/grid_client";
259259
import { type Contract, ContractState, NodeStatus, SortByContracts, SortOrder } from "@threefold/gridproxy_client";
260260
import { DeploymentKeyDeletionError } from "@threefold/types";
261-
import { computed, defineComponent, onMounted, type Ref, ref } from "vue";
261+
import { computed, defineComponent, onMounted, onUnmounted, type Ref, ref } from "vue";
262262
263263
import ContractsTable from "@/components/contracts_list/contracts_table.vue";
264264
import { useProfileManagerController } from "@/components/profile_manager_controller.vue";
@@ -314,6 +314,7 @@ const nodeInfo: Ref<{ [nodeId: number]: { status: NodeStatus } }> = ref({});
314314
const unlockContractLoading = ref<boolean>(false);
315315
const contractsTable = ref<(typeof ContractsTable)[]>([]);
316316
const loadingLockDetails = ref(false);
317+
const timeouts: ReturnType<typeof setTimeout>[] = [];
317318
const nodeIDs = computed<number[]>(() => {
318319
const ids = new Set<number>();
319320
for (const contract of contracts.value) {
@@ -329,6 +330,11 @@ onMounted(() => {
329330
loadContracts();
330331
});
331332
333+
onUnmounted(() => {
334+
timeouts.forEach(timeout => clearTimeout(timeout));
335+
timeouts.length = 0;
336+
});
337+
332338
async function _normalizeContracts(
333339
contracts: Contract[],
334340
contractType: ContractType.Node | ContractType.Name | ContractType.Rent,
@@ -451,10 +457,11 @@ async function unlockAllContracts() {
451457
loadingTablesMessage.value =
452458
"Your request to unlock your contracts has been processed successfully. Changes may take a few minutes to reflect";
453459
createCustomToast(loadingTablesMessage.value, ToastType.info);
454-
setTimeout(() => {
460+
const timeoutId = setTimeout(() => {
455461
loadContracts();
456462
loadingTablesMessage.value = undefined;
457463
}, 30000);
464+
timeouts.push(timeoutId);
458465
unlockDialog.value = false;
459466
} catch (e) {
460467
loadingErrorMessage.value = `Failed to unlock contract your contracts`;
@@ -479,10 +486,11 @@ async function deleteAll() {
479486
loadingTablesMessage.value =
480487
"The contracts have been successfully deleted. Please note that all tables will be reloaded in 30 seconds.";
481488
createCustomToast(loadingTablesMessage.value, ToastType.info);
482-
setTimeout(() => {
489+
const timeoutId = setTimeout(() => {
483490
loadContracts();
484491
loadingTablesMessage.value = undefined;
485492
}, 30000);
493+
timeouts.push(timeoutId);
486494
} catch (e) {
487495
if (e instanceof DeploymentKeyDeletionError) {
488496
createCustomToast("Failed to delete some keys, You don't have enough tokens", ToastType.danger);
@@ -551,10 +559,11 @@ async function onDeletedContracts(_contracts: NormalizedContract[]) {
551559
loadingTablesMessage.value =
552560
"The contracts have been successfully deleted. Please note that all tables will be reloaded in 30 seconds.";
553561
createCustomToast(loadingTablesMessage.value, ToastType.info);
554-
setTimeout(() => {
562+
const timeoutId = setTimeout(() => {
555563
loadContracts();
556564
loadingTablesMessage.value = undefined;
557565
}, 30000);
566+
timeouts.push(timeoutId);
558567
contracts.value = [...rentContracts.value, ...nameContracts.value, ...nodeContracts.value];
559568
}
560569
async function getContractsLockDetails() {

0 commit comments

Comments
 (0)