Merge pull request #5713 from mempool/mononaut/fix-missing-unsubs

clean up subscriptions & component references
This commit is contained in:
softsimon 2025-01-09 16:48:44 +07:00 committed by GitHub
commit c3686a5500
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 81 additions and 36 deletions

View File

@ -46,6 +46,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
aggregatedHistory$: Observable<any>; aggregatedHistory$: Observable<any>;
statsSubscription: Subscription; statsSubscription: Subscription;
aggregatedHistorySubscription: Subscription;
fragmentSubscription: Subscription;
isLoading = true; isLoading = true;
formatNumber = formatNumber; formatNumber = formatNumber;
timespan = ''; timespan = '';
@ -79,8 +81,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
} }
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.route.fragment.subscribe((fragment) => { this.fragmentSubscription = this.route.fragment.subscribe((fragment) => {
if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) { if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
} }
@ -113,7 +115,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
share(), share(),
); );
this.aggregatedHistory$.subscribe(); this.aggregatedHistorySubscription = this.aggregatedHistory$.subscribe();
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
@ -335,8 +337,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.statsSubscription) { this.aggregatedHistorySubscription?.unsubscribe();
this.statsSubscription.unsubscribe(); this.fragmentSubscription?.unsubscribe();
} this.statsSubscription?.unsubscribe();
} }
} }

View File

@ -172,13 +172,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.animationFrameRequest) { if (this.animationFrameRequest) {
cancelAnimationFrame(this.animationFrameRequest); cancelAnimationFrame(this.animationFrameRequest);
clearTimeout(this.animationHeartBeat);
} }
clearTimeout(this.animationHeartBeat);
if (this.canvas) { if (this.canvas) {
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
this.themeChangedSubscription?.unsubscribe();
} }
if (this.scene) {
this.scene.destroy();
}
this.vertexArray.destroy();
this.vertexArray = null;
this.themeChangedSubscription?.unsubscribe();
this.searchSubscription?.unsubscribe();
} }
clear(direction): void { clear(direction): void {
@ -447,7 +453,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
} }
this.applyQueuedUpdates(); this.applyQueuedUpdates();
// skip re-render if there's no change to the scene // skip re-render if there's no change to the scene
if (this.scene && this.gl) { if (this.scene && this.gl && this.vertexArray) {
/* SET UP SHADER UNIFORMS */ /* SET UP SHADER UNIFORMS */
// screen dimensions // screen dimensions
this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight); this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight);
@ -489,9 +495,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) { if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) {
this.doRun(); this.doRun();
} else { } else {
if (this.animationHeartBeat) { clearTimeout(this.animationHeartBeat);
clearTimeout(this.animationHeartBeat);
}
this.animationHeartBeat = window.setTimeout(() => { this.animationHeartBeat = window.setTimeout(() => {
this.start(); this.start();
}, 1000); }, 1000);

View File

@ -19,6 +19,7 @@ export class FastVertexArray {
freeSlots: number[]; freeSlots: number[];
lastSlot: number; lastSlot: number;
dirty = false; dirty = false;
destroyed = false;
constructor(length, stride) { constructor(length, stride) {
this.length = length; this.length = length;
@ -32,6 +33,9 @@ export class FastVertexArray {
} }
insert(sprite: TxSprite): number { insert(sprite: TxSprite): number {
if (this.destroyed) {
return;
}
this.count++; this.count++;
let position; let position;
@ -45,11 +49,14 @@ export class FastVertexArray {
} }
} }
this.sprites[position] = sprite; this.sprites[position] = sprite;
return position;
this.dirty = true; this.dirty = true;
return position;
} }
remove(index: number): void { remove(index: number): void {
if (this.destroyed) {
return;
}
this.count--; this.count--;
this.clearData(index); this.clearData(index);
this.freeSlots.push(index); this.freeSlots.push(index);
@ -61,20 +68,26 @@ export class FastVertexArray {
} }
setData(index: number, dataChunk: number[]): void { setData(index: number, dataChunk: number[]): void {
if (this.destroyed) {
return;
}
this.data.set(dataChunk, (index * this.stride)); this.data.set(dataChunk, (index * this.stride));
this.dirty = true; this.dirty = true;
} }
clearData(index: number): void { private clearData(index: number): void {
this.data.fill(0, (index * this.stride), ((index + 1) * this.stride)); this.data.fill(0, (index * this.stride), ((index + 1) * this.stride));
this.dirty = true; this.dirty = true;
} }
getData(index: number): Float32Array { getData(index: number): Float32Array {
if (this.destroyed) {
return;
}
return this.data.subarray(index, this.stride); return this.data.subarray(index, this.stride);
} }
expand(): void { private expand(): void {
this.length *= 2; this.length *= 2;
const newData = new Float32Array(this.length * this.stride); const newData = new Float32Array(this.length * this.stride);
newData.set(this.data); newData.set(this.data);
@ -82,7 +95,7 @@ export class FastVertexArray {
this.dirty = true; this.dirty = true;
} }
compact(): void { private compact(): void {
// New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512) // New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512)
const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count)))); const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count))));
if (newLength !== this.length) { if (newLength !== this.length) {
@ -110,4 +123,13 @@ export class FastVertexArray {
getVertexData(): Float32Array { getVertexData(): Float32Array {
return this.data; return this.data;
} }
destroy(): void {
this.data = null;
this.sprites = null;
this.freeSlots = null;
this.lastSlot = 0;
this.dirty = false;
this.destroyed = true;
}
} }

View File

@ -116,7 +116,7 @@ export class BlockViewComponent implements OnInit, OnDestroy {
this.isLoadingBlock = false; this.isLoadingBlock = false;
this.isLoadingOverview = true; this.isLoadingOverview = true;
}), }),
shareReplay(1) shareReplay({ bufferSize: 1, refCount: true })
); );
this.overviewSubscription = block$.pipe( this.overviewSubscription = block$.pipe(
@ -176,5 +176,8 @@ export class BlockViewComponent implements OnInit, OnDestroy {
if (this.queryParamsSubscription) { if (this.queryParamsSubscription) {
this.queryParamsSubscription.unsubscribe(); this.queryParamsSubscription.unsubscribe();
} }
if (this.blockGraph) {
this.blockGraph.destroy();
}
} }
} }

View File

@ -117,7 +117,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy {
this.openGraphService.waitOver('block-data-' + this.rawId); this.openGraphService.waitOver('block-data-' + this.rawId);
}), }),
throttleTime(50, asyncScheduler, { leading: true, trailing: true }), throttleTime(50, asyncScheduler, { leading: true, trailing: true }),
shareReplay(1) shareReplay({ bufferSize: 1, refCount: true })
); );
this.overviewSubscription = block$.pipe( this.overviewSubscription = block$.pipe(

View File

@ -1,8 +1,8 @@
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router';
import { ElectrsApiService } from '@app/services/electrs-api.service'; import { ElectrsApiService } from '@app/services/electrs-api.service';
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators'; import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators';
import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs'; import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs';
import { StateService } from '@app/services/state.service'; import { StateService } from '@app/services/state.service';
import { SeoService } from '@app/services/seo.service'; import { SeoService } from '@app/services/seo.service';
@ -68,6 +68,7 @@ export class BlockComponent implements OnInit, OnDestroy {
paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
numUnexpected: number = 0; numUnexpected: number = 0;
mode: 'projected' | 'actual' = 'projected'; mode: 'projected' | 'actual' = 'projected';
currentQueryParams: Params;
overviewSubscription: Subscription; overviewSubscription: Subscription;
accelerationsSubscription: Subscription; accelerationsSubscription: Subscription;
@ -80,8 +81,8 @@ export class BlockComponent implements OnInit, OnDestroy {
timeLtr: boolean; timeLtr: boolean;
childChangeSubscription: Subscription; childChangeSubscription: Subscription;
auditPrefSubscription: Subscription; auditPrefSubscription: Subscription;
isAuditEnabledSubscription: Subscription;
oobSubscription: Subscription; oobSubscription: Subscription;
priceSubscription: Subscription; priceSubscription: Subscription;
blockConversion: Price; blockConversion: Price;
@ -118,7 +119,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.setAuditAvailable(this.auditSupported); this.setAuditAvailable(this.auditSupported);
if (this.auditSupported) { if (this.auditSupported) {
this.isAuditEnabledFromParam().subscribe(auditParam => { this.isAuditEnabledSubscription = this.isAuditEnabledFromParam().subscribe(auditParam => {
if (this.auditParamEnabled) { if (this.auditParamEnabled) {
this.auditModeEnabled = auditParam; this.auditModeEnabled = auditParam;
} else { } else {
@ -281,7 +282,7 @@ export class BlockComponent implements OnInit, OnDestroy {
} }
}), }),
throttleTime(300, asyncScheduler, { leading: true, trailing: true }), throttleTime(300, asyncScheduler, { leading: true, trailing: true }),
shareReplay(1) shareReplay({ bufferSize: 1, refCount: true })
); );
this.overviewSubscription = this.block$.pipe( this.overviewSubscription = this.block$.pipe(
@ -363,6 +364,7 @@ export class BlockComponent implements OnInit, OnDestroy {
.subscribe((network) => this.network = network); .subscribe((network) => this.network = network);
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
this.currentQueryParams = params;
if (params.showDetails === 'true') { if (params.showDetails === 'true') {
this.showDetails = true; this.showDetails = true;
} else { } else {
@ -414,6 +416,7 @@ export class BlockComponent implements OnInit, OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
this.stateService.markBlock$.next({}); this.stateService.markBlock$.next({});
this.overviewSubscription?.unsubscribe(); this.overviewSubscription?.unsubscribe();
this.accelerationsSubscription?.unsubscribe();
this.keyNavigationSubscription?.unsubscribe(); this.keyNavigationSubscription?.unsubscribe();
this.blocksSubscription?.unsubscribe(); this.blocksSubscription?.unsubscribe();
this.cacheBlocksSubscription?.unsubscribe(); this.cacheBlocksSubscription?.unsubscribe();
@ -421,8 +424,16 @@ export class BlockComponent implements OnInit, OnDestroy {
this.queryParamsSubscription?.unsubscribe(); this.queryParamsSubscription?.unsubscribe();
this.timeLtrSubscription?.unsubscribe(); this.timeLtrSubscription?.unsubscribe();
this.childChangeSubscription?.unsubscribe(); this.childChangeSubscription?.unsubscribe();
this.priceSubscription?.unsubscribe(); this.auditPrefSubscription?.unsubscribe();
this.isAuditEnabledSubscription?.unsubscribe();
this.oobSubscription?.unsubscribe(); this.oobSubscription?.unsubscribe();
this.priceSubscription?.unsubscribe();
this.blockGraphProjected.forEach(graph => {
graph.destroy();
});
this.blockGraphActual.forEach(graph => {
graph.destroy();
});
} }
// TODO - Refactor this.fees/this.reward for liquid because it is not // TODO - Refactor this.fees/this.reward for liquid because it is not
@ -733,19 +744,18 @@ export class BlockComponent implements OnInit, OnDestroy {
toggleAuditMode(): void { toggleAuditMode(): void {
this.stateService.hideAudit.next(this.auditModeEnabled); this.stateService.hideAudit.next(this.auditModeEnabled);
this.route.queryParams.subscribe(params => { const queryParams = { ...this.currentQueryParams };
const queryParams = { ...params }; delete queryParams['audit'];
delete queryParams['audit'];
let newUrl = this.router.url.split('?')[0]; let newUrl = this.router.url.split('?')[0];
const queryString = new URLSearchParams(queryParams).toString(); const queryString = new URLSearchParams(queryParams).toString();
if (queryString) { if (queryString) {
newUrl += '?' + queryString; newUrl += '?' + queryString;
} }
this.location.replaceState(newUrl);
this.location.replaceState(newUrl);
});
// avoid duplicate subscriptions
this.auditPrefSubscription?.unsubscribe();
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => { this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
this.auditModeEnabled = !hide; this.auditModeEnabled = !hide;
this.showAudit = this.auditAvailable && this.auditModeEnabled; this.showAudit = this.auditAvailable && this.auditModeEnabled;
@ -762,7 +772,7 @@ export class BlockComponent implements OnInit, OnDestroy {
return this.route.queryParams.pipe( return this.route.queryParams.pipe(
map(params => { map(params => {
this.auditParamEnabled = 'audit' in params; this.auditParamEnabled = 'audit' in params;
return this.auditParamEnabled ? !(params['audit'] === 'false') : true; return this.auditParamEnabled ? !(params['audit'] === 'false') : true;
}) })
); );

View File

@ -162,6 +162,9 @@ export class EightBlocksComponent implements OnInit, OnDestroy {
this.cacheBlocksSubscription?.unsubscribe(); this.cacheBlocksSubscription?.unsubscribe();
this.networkChangedSubscription?.unsubscribe(); this.networkChangedSubscription?.unsubscribe();
this.queryParamsSubscription?.unsubscribe(); this.queryParamsSubscription?.unsubscribe();
this.blockGraphs.forEach(graph => {
graph.destroy();
});
} }
shiftTestBlocks(): void { shiftTestBlocks(): void {

View File

@ -120,6 +120,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.blockGraph?.destroy();
this.blockSub.unsubscribe(); this.blockSub.unsubscribe();
this.timeLtrSubscription.unsubscribe(); this.timeLtrSubscription.unsubscribe();
this.websocketService.stopTrackMempoolBlock(); this.websocketService.stopTrackMempoolBlock();