[BUG] Excessive memory usage for SPA where unload hooks keep accumulating #2311 (#2312)

- Rework Asynchronous notification handling
This commit is contained in:
Nev 2024-03-20 16:44:58 -07:00 коммит произвёл GitHub
Родитель 2eaa2d568f
Коммит 276c123a40
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
41 изменённых файлов: 607 добавлений и 284 удалений

Просмотреть файл

@ -5,8 +5,8 @@ import { Snippet } from "../../../src/Snippet";
import { utlRemoveSessionStorage } from "@microsoft/applicationinsights-common";
export class AISKUSizeCheck extends AITestClass {
private readonly MAX_RAW_SIZE = 139;
private readonly MAX_BUNDLE_SIZE = 139;
private readonly MAX_RAW_SIZE = 140;
private readonly MAX_BUNDLE_SIZE = 140;
private readonly MAX_RAW_DEFLATE_SIZE = 56;
private readonly MAX_BUNDLE_DEFLATE_SIZE = 56;
private readonly rawFilePath = "../dist/es5/applicationinsights-web.min.js";

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -2,6 +2,7 @@ import { AISKUSizeCheck } from "./AISKUSize.Tests";
import { ApplicationInsightsTests } from './applicationinsights.e2e.tests';
import { ApplicationInsightsFetchTests } from './applicationinsights.e2e.fetch.tests';
import { CdnPackagingChecks } from './CdnPackaging.tests';
import { GlobalTestHooks } from './GlobalTestHooks.Test';
import { SanitizerE2ETests } from './sanitizer.e2e.tests';
import { ValidateE2ETests } from './validate.e2e.tests';
import { SenderE2ETests } from './sender.e2e.tests';
@ -10,6 +11,7 @@ import { CdnThrottle} from "./CdnThrottle.tests";
import { ThrottleSentMessage } from "./ThrottleSentMessage.tests";
export function runTests() {
new GlobalTestHooks().registerTests();
new AISKUSizeCheck().registerTests();
new ApplicationInsightsTests().registerTests();
new ApplicationInsightsFetchTests().registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,8 +1,10 @@
import { AISKULightSizeCheck } from "./AISKULightSize.Tests";
import { ApplicationInsightsDynamicConfigTests } from "./dynamicconfig.tests";
import { ApplicationInsightsConfigTests } from "./config.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new AISKULightSizeCheck().registerTests();
new ApplicationInsightsDynamicConfigTests().registerTests();
new ApplicationInsightsConfigTests().registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { _testHookMaxUnloadHooksCb } from "@microsoft/1ds-core-js";
import { Assert } from "@microsoft/ai-test-framework";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -3,8 +3,10 @@ import { HttpManagerTest } from './HttpManagerTest';
import { KillSwitchTest } from './KillSwitchTest';
import { SerializerTest } from './SerializerTest';
import { FileSizeCheckTest } from "./FileSizeCheckTest"
import { GlobalTestHooks } from './GlobalTestHooks.Test';
export function registerTests() {
new GlobalTestHooks().registerTests();
new PostChannelTest("PostChannelTest").registerTests();
new HttpManagerTest("HttpManagerTest").registerTests();
new HttpManagerTest("HttpManagerTest", true).registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,7 +1,9 @@
import { SenderTests } from "./Sender.tests";
import { SampleTests } from "./Sample.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new SenderTests().registerTests();
new SampleTests().registerTests();
}

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -5,8 +5,10 @@ import { OfflineInMemoryBatchTests } from "./inmemorybatch.tests";
import { OfflineBatchHandlerTests } from "./offlinebatchhandler.tests";
import { ChannelTests } from "./channel.tests";
import { Offlinetimer } from "./offlinetimer.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new OfflineIndexedDBTests().registerTests();
new OfflineWebProviderTests().registerTests();
new OfflineDbProviderTests().registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,5 +1,7 @@
import { TeeChannelCoreTests } from "./TeeChannelCore.Tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new TeeChannelCoreTests().registerTests();
}

430
common/config/rush/npm-shrinkwrap.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,8 +1,10 @@
import { AnalyticsPluginTests } from './AnalyticsPlugin.tests';
import { TelemetryItemCreatorTests } from './TelemetryItemCreator.tests';
import { AnalyticsExtensionSizeCheck } from "./AnalyticsExtensionSize.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new AnalyticsPluginTests().registerTests();
new TelemetryItemCreatorTests().registerTests();
new AnalyticsExtensionSizeCheck().registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,7 +1,9 @@
import { CfgSyncHelperTests } from "./cfgsynchelper.tests";
import {CfgSyncPluginTests} from "./cfgsyncplugin.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new CfgSyncPluginTests().registerTests();
new CfgSyncHelperTests().registerTests();
}

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,5 +1,7 @@
import { ClickEventTest } from './ClickEventTest';
import { GlobalTestHooks } from './GlobalTestHooks.Test';
export function runTests() {
new GlobalTestHooks().registerTests();
new ClickEventTest().registerTests();
}

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,6 +1,8 @@
import { AjaxTests, AjaxPerfTrackTests, AjaxFrozenTests } from "./ajax.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new AjaxTests().registerTests();
new AjaxPerfTrackTests().registerTests();
new AjaxFrozenTests().registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,5 +1,7 @@
import { MarkMeasureTests } from './MarkMeasureTests';
import { GlobalTestHooks } from './GlobalTestHooks.Test';
export function runTests() {
new GlobalTestHooks().registerTests();
new MarkMeasureTests().registerTests();
}

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -3,8 +3,10 @@ import { PropertiesTests } from "./properties.tests";
import { SessionManagerTests } from "./SessionManager.Tests";
import { PropertiesExtensionSizeCheck } from "./propertiesSize.tests";
import { TelemetryContextTests } from "./TelemetryContext.Tests";
import { GlobalTestHooks } from './GlobalTestHooks.Test';
export function runTests() {
new GlobalTestHooks().registerTests();
new PropertiesTests().registerTests();
new SessionManagerTests(false).registerTests();
new SessionManagerTests(true).registerTests();

Просмотреть файл

@ -69,7 +69,10 @@ export {
IPayloadData, IXHROverride, OnCompleteCallback, SendPOSTFunction, IInternalOfflineSupport, _ISendPostMgrConfig, IBackendResponse, _ISenderOnComplete, SenderPostManager,
getResponseText, formatErrorMessageXdr, formatErrorMessageXhr, prependTransports, parseResponse, convertAllHeadersToMap, _getAllResponseHeaders, _appendHeader, _IInternalXhrOverride,
_ITimeoutOverrideWrapper, IXDomainRequest,
TransportType
TransportType,
// Test Hooks
_testHookMaxUnloadHooksCb
} from "@microsoft/applicationinsights-core-js";
export {

Просмотреть файл

@ -2,7 +2,7 @@ import { AITestClass } from "@microsoft/ai-test-framework";
import * as pako from 'pako';
export class FileSizeCheckTest extends AITestClass {
private readonly MAX_BUNDLE_SIZE = 65;
private readonly MAX_BUNDLE_SIZE = 66;
private readonly MAX_DEFLATE_SIZE = 28;
private readonly bundleFilePath = "../bundle/es5/ms.core.min.js";

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -5,10 +5,12 @@ import { DynamicProtoTests } from './DynamicProtoTests';
import { UtilsTest } from './UtilsTest';
import { ValueSanitizerTests } from './ValueSanitizerTests';
import {FileSizeCheckTest} from './FileSizeCheckTest'
import { GlobalTestHooks } from './GlobalTestHooks.Test';
export function registerTests() {
new GlobalTestHooks().registerTests();
new CoreTest('CoreTest').registerTests();
new ESPromiseTests('ESPromiseTests').registerTests();
new ESPromiseSchedulerTests('ESPromiseSchedulerTests').registerTests();

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "@microsoft/applicationinsights-core-js";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -5,8 +5,10 @@ import { ConnectionStringParserTests } from "./ConnectionStringParser.tests";
import { SeverityLevelTests } from "./SeverityLevel.tests";
import { RequestHeadersTests } from "./RequestHeaders.tests";
import { ThrottleMgrTest } from "./ThrottleMgr.tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
export function runTests() {
new GlobalTestHooks().registerTests();
new ThrottleMgrTest().registerTests();
new ApplicationInsightsTests().registerTests();
new ExceptionTests().registerTests();

Просмотреть файл

@ -1,5 +1,5 @@
import { Assert, AITestClass } from "@microsoft/ai-test-framework";
import { IConfiguration, ITelemetryPlugin, ITelemetryItem, IPlugin, IAppInsightsCore, normalizeJsName, random32, mwcRandomSeed, newId, randomValue, mwcRandom32, isNullOrUndefined, SenderPostManager, OnCompleteCallback, IPayloadData, _ISenderOnComplete, TransportType, _ISendPostMgrConfig } from "../../../src/applicationinsights-core-js"
import { IConfiguration, ITelemetryPlugin, ITelemetryItem, IPlugin, IAppInsightsCore, normalizeJsName, random32, mwcRandomSeed, newId, randomValue, mwcRandom32, isNullOrUndefined, SenderPostManager, OnCompleteCallback, IPayloadData, _ISenderOnComplete, TransportType, _ISendPostMgrConfig, dumpObj } from "../../../src/applicationinsights-core-js"
import { AppInsightsCore } from "../../../src/JavaScriptSDK/AppInsightsCore";
import { IChannelControls } from "../../../src/JavaScriptSDK.Interfaces/IChannelControls";
import { _eInternalMessageId, LoggingSeverity } from "../../../src/JavaScriptSDK.Enums/LoggingEnums";
@ -19,7 +19,6 @@ export class ApplicationInsightsCoreTests extends AITestClass {
}
public registerTests() {
this.testCase({
name: "ApplicationInsightsCore: Initialization validates input",
test: () => {
@ -999,6 +998,30 @@ export class ApplicationInsightsCoreTests extends AITestClass {
}
});
this.testCase({
name: 'Test Excessive unload hook detection - make sure calling getPerfMgr() does not cause excessive unload hook detection',
test: () => {
const appInsightsCore = new AppInsightsCore();
const channelPlugin1 = new ChannelPlugin();
channelPlugin1.priority = 1001;
const theConfig = {
channels: [[channelPlugin1]],
endpointUrl: "https://dc.services.visualstudio.com/v2/track",
instrumentationKey: "",
extensionConfig: {}
};
appInsightsCore.initialize(theConfig, []);
Assert.equal(true, appInsightsCore.isInitialized(), "Core is initialized");
// Send lots of notifications
for (let lp = 0; lp < 100; lp++) {
Assert.equal(null, appInsightsCore.getPerfMgr());
}
}
});
function _createBuckets(num: number) {
// Using helper function as TypeScript 2.5.3 is complaining about new Array<number>(100).fill(0);
let buckets: number[] = [];

Просмотреть файл

@ -0,0 +1,13 @@
import { Assert } from "@microsoft/ai-test-framework";
import { _testHookMaxUnloadHooksCb } from "../../../src/JavaScriptSDK/UnloadHookContainer";
import { dumpObj } from "@nevware21/ts-utils";
export class GlobalTestHooks {
public registerTests() {
// Set a global maximum
_testHookMaxUnloadHooksCb(20, (state: string, hooks: Array<any>) => {
Assert.ok(false, "Max unload hooks exceeded [" + hooks.length + "] - " + state + " - " + dumpObj(hooks));
});
}
}

Просмотреть файл

@ -1,6 +1,7 @@
import '@microsoft/applicationinsights-shims';
import { ApplicationInsightsCoreTests } from "./ApplicationInsightsCore.Tests";
import { CookieManagerTests } from "./CookieManager.Tests";
import { GlobalTestHooks } from "./GlobalTestHooks.Test";
import { HelperFuncTests } from './HelperFunc.Tests';
import { AppInsightsCoreSizeCheck } from "./AppInsightsCoreSize.Tests";
import { EventHelperTests } from "./EventHelper.Tests";
@ -12,6 +13,7 @@ import { W3cTraceParentTests } from "./W3cTraceParentTests";
import { DynamicConfigTests } from "./DynamicConfig.Tests";
export function runTests() {
new GlobalTestHooks().registerTests();
new DynamicTests().registerTests();
new DynamicConfigTests().registerTests();
new ApplicationInsightsCoreTests().registerTests();

Просмотреть файл

@ -36,6 +36,8 @@ function _createAndUseHandler<T>(state: _IDynamicConfigHandlerState<T>, configHa
}
};
objDefine<any>(handler, "toJSON", { v: () => "WatcherHandler" + (handler.fn ? "" : "[X]") });
state.use(handler, configHandler);
return handler;

Просмотреть файл

@ -84,6 +84,7 @@ export const enum _eInternalMessageId {
InvalidDurationValue = 45,
TelemetryEnvelopeInvalid = 46,
CreateEnvelopeError = 47,
MaxUnloadHookExceeded = 48,
// User actionable
CannotSerializeObject = 48,

Просмотреть файл

@ -181,7 +181,7 @@ function _addDelayedCfgListener(listeners: { rm: () => void, w: WatcherFunction<
let theListener = _findWatcher(listeners, newWatcher).l;
if (!theListener) {
theListener ={
theListener = {
w: newWatcher,
rm: () => {
let fnd = _findWatcher(listeners, newWatcher);
@ -207,6 +207,36 @@ function _registerDelayedCfgListener(config: IConfiguration, listeners: { rm: ()
});
}
// Moved this outside of the closure to reduce the retained memory footprint
function _initDebugListener(configHandler: IDynamicConfigHandler<IConfiguration>, unloadContainer: IUnloadHookContainer, notificationManager: INotificationManager, debugListener: INotificationListener) {
// Will get recalled if any referenced config values are changed
unloadContainer.add(configHandler.watch((details) => {
let disableDbgExt = details.cfg.disableDbgExt;
if (disableDbgExt === true && debugListener) {
// Remove any previously loaded debug listener
notificationManager.removeNotificationListener(debugListener);
debugListener = null;
}
if (notificationManager && !debugListener && disableDbgExt !== true) {
debugListener = getDebugListener(details.cfg);
notificationManager.addNotificationListener(debugListener);
}
}));
return debugListener
}
// Moved this outside of the closure to reduce the retained memory footprint
function _createUnloadHook(unloadHook: IUnloadHook): IUnloadHook {
return objDefine<IUnloadHook | any>({
rm: () => {
unloadHook.rm();
}
}, "toJSON", { v: () => "aicore::onCfgChange<" + JSON.stringify(unloadHook) + ">" });
}
/**
* @group Classes
* @group Entrypoint
@ -317,7 +347,8 @@ export class AppInsightsCore<CfgType extends IConfiguration = IConfiguration> im
_notificationManager = notificationManager;
_initDebugListener();
// Initialize the debug listener outside of the closure to reduce the retained memory footprint
_debugListener = _initDebugListener(_configHandler, _hookContainer, _notificationManager && _self.getNotifyMgr(), _debugListener);
_initPerfManager();
_self.logger = logger;
@ -444,17 +475,6 @@ export class AppInsightsCore<CfgType extends IConfiguration = IConfiguration> im
};
_self.getPerfMgr = (): IPerfManager => {
if (!_perfManager && !_cfgPerfManager) {
_addUnloadHook(_configHandler.watch((details) => {
if (details.cfg.enablePerfMgr) {
let createPerfMgr = details.cfg.createPerfMgr;
if (isFunction(createPerfMgr)) {
_cfgPerfManager = createPerfMgr(_self, _self.getNotifyMgr());
}
}
}));
}
return _perfManager || _cfgPerfManager || getGblPerfMgr();
};
@ -726,11 +746,7 @@ export class AppInsightsCore<CfgType extends IConfiguration = IConfiguration> im
unloadHook = onConfigChange(_configHandler.cfg, handler, _self.logger);
}
return {
rm: () => {
unloadHook.rm();
}
}
return _createUnloadHook(unloadHook);
};
_self.getWParam = () => {
@ -854,6 +870,8 @@ export class AppInsightsCore<CfgType extends IConfiguration = IConfiguration> im
_pluginVersionString = null;
_pluginVersionStringArr = null;
_forceStopInternalLogPoller = false;
_internalLogPoller = null;
_internalLogPollerListening = false;
}
function _createTelCtx(): IProcessTelemetryContext {
@ -1106,40 +1124,39 @@ export class AppInsightsCore<CfgType extends IConfiguration = IConfiguration> im
return true;
}
function _initDebugListener() {
// Lazily ensure that the notification manager is created
!_notificationManager && _self.getNotifyMgr();
// Will get recalled if any referenced config values are changed
_addUnloadHook(_configHandler.watch((details) => {
let disableDbgExt = details.cfg.disableDbgExt;
if (disableDbgExt === true && _debugListener) {
// Remove any previously loaded debug listener
_notificationManager.removeNotificationListener(_debugListener);
_debugListener = null;
}
if (_notificationManager && !_debugListener && disableDbgExt !== true) {
_debugListener = getDebugListener(details.cfg);
_notificationManager.addNotificationListener(_debugListener);
}
}));
}
function _initPerfManager() {
// Save the previous config based performance manager creator to avoid creating new perf manager instances if unchanged
let prevCfgPerfMgr: (core: IAppInsightsCore, notificationManager: INotificationManager) => IPerfManager;
// Will get recalled if any referenced config values are changed
_addUnloadHook(_configHandler.watch((details) => {
let enablePerfMgr = details.cfg.enablePerfMgr;
if (enablePerfMgr) {
let createPerfMgr = details.cfg.createPerfMgr;
if (prevCfgPerfMgr !== createPerfMgr) {
if (!createPerfMgr) {
createPerfMgr = _createPerfManager;
}
if (!enablePerfMgr && _cfgPerfManager) {
// Set the performance manager creation function if not defined
getSetValue(details.cfg, STR_CREATE_PERF_MGR, createPerfMgr);
prevCfgPerfMgr = createPerfMgr;
// Remove any existing config based performance manager
_cfgPerfManager = null;
}
// Only create the performance manager if it's not already created or manually set
if (!_perfManager && !_cfgPerfManager && isFunction(createPerfMgr)) {
// Create a new config based performance manager
_cfgPerfManager = createPerfMgr(_self, _self.getNotifyMgr());
}
} else {
// Remove any existing config based performance manager
_cfgPerfManager = null;
}
if (enablePerfMgr) {
// Set the performance manager creation function if not defined
getSetValue(details.cfg, STR_CREATE_PERF_MGR, _createPerfManager);
// Clear the previous cached value so it can be GC'd
prevCfgPerfMgr = null;
}
}));
}

Просмотреть файл

@ -2,7 +2,7 @@
// Licensed under the MIT License.
import dynamicProto from "@microsoft/dynamicproto-js";
import { IPromise, createAllPromise, createPromise, doAwaitResponse } from "@nevware21/ts-async";
import { arrForEach, arrIndexOf, objDefine, scheduleTimeout } from "@nevware21/ts-utils";
import { ITimerHandler, arrForEach, arrIndexOf, objDefine, safe, scheduleTimeout } from "@nevware21/ts-utils";
import { createDynamicConfig } from "../Config/DynamicConfig";
import { IConfiguration } from "../JavaScriptSDK.Interfaces/IConfiguration";
import { INotificationListener } from "../JavaScriptSDK.Interfaces/INotificationListener";
@ -18,17 +18,37 @@ const defaultValues = {
perfEvtsSendAll: false
};
function _runListeners(listeners: INotificationListener[], name: string, isAsync: boolean, callback: (listener: INotificationListener) => void) {
interface IAsyncNotifications {
h: ITimerHandler;
cb: Array<{ fn: (listener: INotificationListener) => void, arg: INotificationListener }>
}
function _runScheduledListeners(asyncNotifications: IAsyncNotifications) {
asyncNotifications.h = null;
let callbacks = asyncNotifications.cb;
asyncNotifications.cb = [];
arrForEach(callbacks, (cb) => {
// Run the listener in a try-catch to ensure that a single listener failing doesn't prevent the others from running
safe(cb.fn, [cb.arg]);
});
}
// This function is used to combine the logic of running the listeners and handling the async notifications so that they don't
// create multiple timers if there are multiple async listeners.
function _runListeners(listeners: INotificationListener[], name: string, asyncNotifications: IAsyncNotifications | null, callback: (listener: INotificationListener) => void) {
arrForEach(listeners, (listener) => {
if (listener && listener[name]) {
if (isAsync) {
scheduleTimeout(() => callback(listener), 0);
if (asyncNotifications) {
// Schedule the callback to be called after the current call stack has cleared.
asyncNotifications.cb.push({
fn: callback,
arg: listener
});
asyncNotifications.h = asyncNotifications.h || scheduleTimeout(_runScheduledListeners, 0, asyncNotifications);
} else {
try {
callback(listener);
} catch (e) {
// Catch errors to ensure we don't block sending the requests
}
// Run the listener in a try-catch to ensure that a single listener failing doesn't prevent the others from running
safe(callback, [listener]);
}
}
});
@ -44,7 +64,11 @@ export class NotificationManager implements INotificationManager {
let perfEvtsSendAll: boolean;
let unloadHandler: IUnloadHook;
let _listeners: INotificationListener[] = [];
let _asyncNotifications: IAsyncNotifications = {
h: null,
cb: []
};
let cfgHandler = createDynamicConfig(config, defaultValues);
unloadHandler = cfgHandler.watch((details) => {
@ -77,7 +101,7 @@ export class NotificationManager implements INotificationManager {
* @param events - The array of events that have been sent.
*/
_self.eventsSent = (events: ITelemetryItem[]): void => {
_runListeners(_listeners, STR_EVENTS_SENT, true, (listener) => {
_runListeners(_listeners, STR_EVENTS_SENT, _asyncNotifications, (listener) => {
listener.eventsSent(events);
});
};
@ -89,7 +113,7 @@ export class NotificationManager implements INotificationManager {
* constant should be used to check the different values.
*/
_self.eventsDiscarded = (events: ITelemetryItem[], reason: number): void => {
_runListeners(_listeners, STR_EVENTS_DISCARDED, true, (listener) => {
_runListeners(_listeners, STR_EVENTS_DISCARDED, _asyncNotifications, (listener) => {
listener.eventsDiscarded(events, reason);
});
};
@ -100,7 +124,7 @@ export class NotificationManager implements INotificationManager {
* @param isAsync - A flag which identifies whether the requests are being sent in an async or sync manner.
*/
_self.eventsSendRequest = (sendReason: number, isAsync: boolean): void => {
_runListeners(_listeners, STR_EVENTS_SEND_REQUEST, isAsync, (listener) => {
_runListeners(_listeners, STR_EVENTS_SEND_REQUEST, isAsync ? _asyncNotifications : null, (listener) => {
listener.eventsSendRequest(sendReason, isAsync);
});
};
@ -110,7 +134,7 @@ export class NotificationManager implements INotificationManager {
// Send all events or only parent events
if (perfEvtsSendAll || !perfEvent.isChildEvt()) {
_runListeners(_listeners, STR_PERF_EVENT, false, (listener) => {
_runListeners(_listeners, STR_PERF_EVENT, null, (listener) => {
if (perfEvent.isAsync) {
scheduleTimeout(() => listener.perfEvent(perfEvent), 0);
} else {
@ -123,7 +147,7 @@ export class NotificationManager implements INotificationManager {
_self.offlineEventsStored = (events: ITelemetryItem[]): void => {
if (events && events.length) {
_runListeners(_listeners, STR_OFFLINE_STORE, true, (listener) => {
_runListeners(_listeners, STR_OFFLINE_STORE, _asyncNotifications, (listener) => {
listener.offlineEventsStored(events);
});
}
@ -131,7 +155,7 @@ export class NotificationManager implements INotificationManager {
_self.offlineBatchSent = (batch: IPayloadData): void => {
if (batch && batch.data) {
_runListeners(_listeners, STR_OFFLINE_SENT, true, (listener) => {
_runListeners(_listeners, STR_OFFLINE_SENT, _asyncNotifications, (listener) => {
listener.offlineBatchSent(batch);
});
}
@ -140,7 +164,7 @@ export class NotificationManager implements INotificationManager {
_self.offlineBatchDrop = (cnt: number, reason?: number): void => {
if (cnt > 0) {
let rn = reason || 0; // default is unknown
_runListeners(_listeners, STR_OFFLINE_DROP, true, (listener) => {
_runListeners(_listeners, STR_OFFLINE_DROP, _asyncNotifications, (listener) => {
listener.offlineBatchDrop(cnt, rn);
});
}
@ -152,10 +176,15 @@ export class NotificationManager implements INotificationManager {
unloadHandler && unloadHandler.rm();
unloadHandler = null;
_listeners = [];
// Clear any async listener
_asyncNotifications.h && _asyncNotifications.h.cancel();
_asyncNotifications.h = null;
_asyncNotifications.cb = [];
};
let waiting: IPromise<void>[];
_runListeners(_listeners, "unload", false, (listener) => {
_runListeners(_listeners, "unload", null, (listener) => {
let asyncUnload = listener.unload(isAsync);
if (asyncUnload) {
if (!waiting) {

Просмотреть файл

@ -7,6 +7,9 @@ import { IDiagnosticLogger } from "../JavaScriptSDK.Interfaces/IDiagnosticLogger
import { ILegacyUnloadHook, IUnloadHook } from "../JavaScriptSDK.Interfaces/IUnloadHook";
import { _throwInternal } from "./DiagnosticLogger";
let _maxHooks: number | undefined;
let _hookAddMonitor: (state: string, hooks: Array<ILegacyUnloadHook | IUnloadHook>) => void | undefined;
/**
* Interface which identifiesAdd this hook so that it is automatically removed during unloading
* @param hooks - The single hook or an array of IInstrumentHook objects
@ -16,6 +19,17 @@ export interface IUnloadHookContainer {
run: (logger?: IDiagnosticLogger) => void;
}
/**
* Test hook for setting the maximum number of unload hooks and calling a monitor function when the hooks are added or removed
* This allows for automatic test failure when the maximum number of unload hooks is exceeded
* @param maxHooks - The maximum number of unload hooks
* @param addMonitor - The monitor function to call when hooks are added or removed
*/
export function _testHookMaxUnloadHooksCb(maxHooks?: number, addMonitor?: (state: string, hooks: Array<ILegacyUnloadHook | IUnloadHook>) => void) {
_maxHooks = maxHooks;
_hookAddMonitor = addMonitor;
}
/**
* Create a IUnloadHookContainer which can be used to remember unload hook functions to be executed during the component unloading
* process.
@ -37,11 +51,18 @@ export function createUnloadHookContainer(): IUnloadHookContainer {
_throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.PluginException, "Unloading:" + dumpObj(e));
}
});
if (_maxHooks && oldHooks.length > _maxHooks) {
_hookAddMonitor ? _hookAddMonitor("doUnload", oldHooks) : _throwInternal(null, eLoggingSeverity.CRITICAL, _eInternalMessageId.MaxUnloadHookExceeded, "Max unload hooks exceeded. An excessive number of unload hooks has been detected.");
}
}
function _addHook(hooks: IUnloadHook | IUnloadHook[] | Iterator<IUnloadHook> | ILegacyUnloadHook | ILegacyUnloadHook[] | Iterator<ILegacyUnloadHook>) {
if (hooks) {
arrAppend(_hooks, hooks);
if (_maxHooks && _hooks.length > _maxHooks) {
_hookAddMonitor ? _hookAddMonitor("Add", _hooks) : _throwInternal(null, eLoggingSeverity.CRITICAL, _eInternalMessageId.MaxUnloadHookExceeded, "Max unload hooks exceeded. An excessive number of unload hooks has been detected.");
}
}
}

Просмотреть файл

@ -86,7 +86,7 @@ export { getDebugListener, getDebugExt } from "./JavaScriptSDK/DbgExtensionUtils
export { TelemetryInitializerFunction, ITelemetryInitializerHandler, ITelemetryInitializerContainer } from "./JavaScriptSDK.Interfaces/ITelemetryInitializers";
export { createUniqueNamespace } from "./JavaScriptSDK/DataCacheHelper";
export { UnloadHandler, IUnloadHandlerContainer, createUnloadHandlerContainer } from "./JavaScriptSDK/UnloadHandlerContainer";
export { IUnloadHookContainer, createUnloadHookContainer } from "./JavaScriptSDK/UnloadHookContainer";
export { IUnloadHookContainer, createUnloadHookContainer, _testHookMaxUnloadHooksCb } from "./JavaScriptSDK/UnloadHookContainer";
export { ITelemetryUpdateState } from "./JavaScriptSDK.Interfaces/ITelemetryUpdateState";
export { ITelemetryUnloadState } from "./JavaScriptSDK.Interfaces/ITelemetryUnloadState";
export { IDistributedTraceContext } from "./JavaScriptSDK.Interfaces/IDistributedTraceContext";