SQL-Migration extension - fix refresh reentrancy hang (#16482)

* Fix application hang on refresh

* fix re-entrancy issue with refresh

* adding additional refresh checking
This commit is contained in:
brian-harris
2021-07-29 16:44:56 -07:00
committed by GitHub
parent 251d250523
commit e3c7e06983
4 changed files with 49 additions and 22 deletions

View File

@@ -49,6 +49,8 @@ export class DashboardWidget {
private _autoRefreshHandle!: NodeJS.Timeout; private _autoRefreshHandle!: NodeJS.Timeout;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
private isRefreshing: boolean = false;
constructor() { constructor() {
} }
@@ -242,14 +244,19 @@ export class DashboardWidget {
} }
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void { private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
let classVariable = this; const classVariable = this;
clearInterval(this._autoRefreshHandle); clearInterval(this._autoRefreshHandle);
if (interval !== -1) { if (interval !== -1) {
this._autoRefreshHandle = setInterval(function () { classVariable.refreshMigrations(); }, interval); this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshMigrations(); }, interval);
} }
} }
private async refreshMigrations(): Promise<void> { private async refreshMigrations(): Promise<void> {
if (this.isRefreshing) {
return;
}
this.isRefreshing = true;
this._viewAllMigrationsButton.enabled = false; this._viewAllMigrationsButton.enabled = false;
this._migrationStatusCardLoadingContainer.loading = true; this._migrationStatusCardLoadingContainer.loading = true;
try { try {
@@ -304,6 +311,7 @@ export class DashboardWidget {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
this.isRefreshing = false;
this._migrationStatusCardLoadingContainer.loading = false; this._migrationStatusCardLoadingContainer.loading = false;
this._viewAllMigrationsButton.enabled = true; this._viewAllMigrationsButton.enabled = true;
} }

View File

@@ -44,6 +44,8 @@ export class MigrationCutoverDialog {
private _autoRefreshHandle!: any; private _autoRefreshHandle!: any;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
private isRefreshing = false;
readonly _infoFieldWidth: string = '250px'; readonly _infoFieldWidth: string = '250px';
constructor(migration: MigrationContext) { constructor(migration: MigrationContext) {
@@ -279,8 +281,8 @@ export class MigrationCutoverDialog {
d => { try { d.dispose(); } catch { } }); d => { try { d.dispose(); } catch { } });
})); }));
return view.initializeModel(form).then((value) => { return view.initializeModel(form).then(async (value) => {
this.refreshStatus(); await this.refreshStatus();
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@@ -493,13 +495,18 @@ export class MigrationCutoverDialog {
const classVariable = this; const classVariable = this;
clearInterval(this._autoRefreshHandle); clearInterval(this._autoRefreshHandle);
if (interval !== -1) { if (interval !== -1) {
this._autoRefreshHandle = setInterval(function () { classVariable.refreshStatus(); }, interval); this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshStatus(); }, interval);
} }
} }
private async refreshStatus(): Promise<void> { private async refreshStatus(): Promise<void> {
if (this.isRefreshing) {
return;
}
try { try {
this.isRefreshing = true;
this._refreshLoader.loading = true; this._refreshLoader.loading = true;
this._cutoverButton.enabled = false; this._cutoverButton.enabled = false;
this._cancelButton.enabled = false; this._cancelButton.enabled = false;
@@ -615,8 +622,10 @@ export class MigrationCutoverDialog {
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} finally {
this.isRefreshing = false;
this._refreshLoader.loading = false;
} }
this._refreshLoader.loading = false;
} }
private createInfoField(label: string, value: string): { private createInfoField(label: string, value: string): {

View File

@@ -33,6 +33,8 @@ export class MigrationStatusDialog {
private _autoRefreshHandle!: NodeJS.Timeout; private _autoRefreshHandle!: NodeJS.Timeout;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
private isRefreshing = false;
constructor(migrations: MigrationContext[], private _filter: AdsMigrationStatus) { constructor(migrations: MigrationContext[], private _filter: AdsMigrationStatus) {
this._model = new MigrationStatusDialogModel(migrations); this._model = new MigrationStatusDialogModel(migrations);
this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide'); this._dialogObject = azdata.window.createModelViewDialog(loc.MIGRATION_STATUS, 'MigrationControllerDialog', 'wide');
@@ -109,19 +111,16 @@ export class MigrationStatusDialog {
})); }));
this._refresh = this._view.modelBuilder.button().withProps({ this._refresh = this._view.modelBuilder.button().withProps({
iconPath: { iconPath: IconPathHelper.refresh,
light: IconPathHelper.refresh.light,
dark: IconPathHelper.refresh.dark
},
iconHeight: '16px', iconHeight: '16px',
iconWidth: '20px', iconWidth: '20px',
height: '30px', height: '30px',
label: loc.REFRESH_BUTTON_LABEL, label: loc.REFRESH_BUTTON_LABEL,
}).component(); }).component();
this._disposables.push(this._refresh.onDidClick((e) => { this._disposables.push(
this.refreshTable(); this._refresh.onDidClick(
})); async (e) => { await this.refreshTable(); }));
const flexContainer = this._view.modelBuilder.flexContainer().withProps({ const flexContainer = this._view.modelBuilder.flexContainer().withProps({
width: 900, width: 900,
@@ -169,7 +168,7 @@ export class MigrationStatusDialog {
const classVariable = this; const classVariable = this;
clearInterval(this._autoRefreshHandle); clearInterval(this._autoRefreshHandle);
if (interval !== -1) { if (interval !== -1) {
this._autoRefreshHandle = setInterval(function () { classVariable.refreshTable(); }, interval); this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshTable(); }, interval);
} }
} }
@@ -447,11 +446,22 @@ export class MigrationStatusDialog {
} }
private async refreshTable(): Promise<void> { private async refreshTable(): Promise<void> {
this._refreshLoader.loading = true; if (this.isRefreshing) {
const currentConnection = await azdata.connection.getCurrentConnection(); return;
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true); }
await this.populateMigrationTable();
this._refreshLoader.loading = false; this.isRefreshing = true;
try {
this._refreshLoader.loading = true;
const currentConnection = await azdata.connection.getCurrentConnection();
this._model._migrations = await MigrationLocalStorage.getMigrationsBySourceConnections(currentConnection, true);
await this.populateMigrationTable();
} catch (e) {
console.log(e);
} finally {
this.isRefreshing = false;
this._refreshLoader.loading = false;
}
} }
private createStatusTable(): azdata.DeclarativeTableComponent { private createStatusTable(): azdata.DeclarativeTableComponent {

View File

@@ -16,7 +16,7 @@ export class MigrationLocalStorage {
} }
public static async getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile, refreshStatus?: boolean): Promise<MigrationContext[]> { public static async getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile, refreshStatus?: boolean): Promise<MigrationContext[]> {
const undefinedSessionId = '{undefined}';
const result: MigrationContext[] = []; const result: MigrationContext[] = [];
const validMigrations: MigrationContext[] = []; const validMigrations: MigrationContext[] = [];
@@ -32,7 +32,7 @@ export class MigrationLocalStorage {
migration.azureAccount, migration.azureAccount,
migration.subscription, migration.subscription,
migration.migrationContext, migration.migrationContext,
migration.sessionId! migration.sessionId ?? undefinedSessionId
); );
migration.migrationContext.properties.sourceDatabaseName = sourceDatabase; migration.migrationContext.properties.sourceDatabaseName = sourceDatabase;
migration.migrationContext.properties.backupConfiguration = backupConfiguration; migration.migrationContext.properties.backupConfiguration = backupConfiguration;
@@ -41,7 +41,7 @@ export class MigrationLocalStorage {
migration.azureAccount, migration.azureAccount,
migration.subscription, migration.subscription,
migration.asyncUrl, migration.asyncUrl,
migration.sessionId! migration.sessionId ?? undefinedSessionId
); );
} }
} }