From 9d384541e275a2a5b24fde27a207e70e075b0ae4 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Fri, 6 Mar 2026 22:57:22 -0600 Subject: [PATCH 1/3] fix(catalog): propagate AbortSignal in MicrosoftGraphOrgEntityProvider to fix stuck scheduler task Signed-off-by: Lokesh Kaki --- .../src/microsoftGraph/client.ts | 45 ++++++- .../src/microsoftGraph/read.test.ts | 115 ++++++++++++++---- .../src/microsoftGraph/read.ts | 29 ++++- .../MicrosoftGraphOrgEntityProvider.test.ts | 46 +++++++ .../MicrosoftGraphOrgEntityProvider.ts | 7 +- 5 files changed, 205 insertions(+), 37 deletions(-) diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 94f4e88df1..1889fc130b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -128,6 +128,7 @@ export class MicrosoftGraphClient { path: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { // upgrade to advanced query mode transparently when "search" is used // to stay backwards compatible. @@ -151,7 +152,7 @@ export class MicrosoftGraphClient { } : {}; - let response = await this.requestApi(path, query, headers); + let response = await this.requestApi(path, query, headers, signal); for (;;) { if (response.status !== 200) { @@ -170,7 +171,12 @@ export class MicrosoftGraphClient { return; } - response = await this.requestRaw(result['@odata.nextLink'], headers); + response = await this.requestRaw( + result['@odata.nextLink'], + headers, + 2, + signal, + ); } } @@ -186,6 +192,7 @@ export class MicrosoftGraphClient { path: string, query?: ODataQuery, headers?: Record, + signal?: AbortSignal, ): Promise { const queryString = qs.stringify( { @@ -205,6 +212,8 @@ export class MicrosoftGraphClient { return await this.requestRaw( `${this.baseUrl}/${path}${queryString}`, headers, + 2, + signal, ); } @@ -218,6 +227,7 @@ export class MicrosoftGraphClient { url: string, headers?: Record, retryCount = 2, + signal?: AbortSignal, ): Promise { // Make sure that we always have a valid access token (might be cached) const urlObj = new URL(url); @@ -235,10 +245,11 @@ export class MicrosoftGraphClient { ...headers, Authorization: `Bearer ${token.token}`, }, + signal, }); } catch (e: any) { if (e?.code === 'ETIMEDOUT' && retryCount > 0) { - return this.requestRaw(url, headers, retryCount - 1); + return this.requestRaw(url, headers, retryCount - 1, signal); } throw e; } @@ -280,8 +291,14 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path: string = 'users', + signal?: AbortSignal, ): AsyncIterable { - yield* this.requestCollection(path, query, queryMode); + yield* this.requestCollection( + path, + query, + queryMode, + signal, + ); } /** @@ -320,8 +337,14 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path: string = 'groups', + signal?: AbortSignal, ): AsyncIterable { - yield* this.requestCollection(path, query, queryMode); + yield* this.requestCollection( + path, + query, + queryMode, + signal, + ); } /** @@ -336,11 +359,13 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { yield* this.requestCollection( `groups/${groupId}/members`, query, queryMode, + signal, ); } @@ -357,11 +382,13 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable { yield* this.requestCollection( `groups/${groupId}/members/microsoft.graph.user/`, query, queryMode, + signal, ); } @@ -374,8 +401,14 @@ export class MicrosoftGraphClient { */ async getOrganization( tenantId: string, + signal?: AbortSignal, ): Promise { - const response = await this.requestApi(`organization/${tenantId}`); + const response = await this.requestApi( + `organization/${tenantId}`, + undefined, + undefined, + signal, + ); if (response.status !== 200) { await this.handleError(`organization/${tenantId}`, response); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index bd58881728..d4fe3783db 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -140,6 +140,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -188,6 +189,7 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -232,6 +234,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -280,6 +283,7 @@ describe('read microsoft graph', () => { }, undefined, '/users/x/y', + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith( @@ -332,6 +336,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -339,6 +344,7 @@ describe('read microsoft graph', () => { 'groupid', { top: 999 }, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -391,6 +397,7 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -398,6 +405,7 @@ describe('read microsoft graph', () => { 'groupid', { top: 999 }, 'advanced', + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -447,6 +455,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1); @@ -457,6 +466,7 @@ describe('read microsoft graph', () => { top: 999, }, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); @@ -509,6 +519,7 @@ describe('read microsoft graph', () => { }, undefined, '/groups/x/y', + undefined, ); }); }); @@ -545,7 +556,10 @@ describe('read microsoft graph', () => { ); expect(client.getOrganization).toHaveBeenCalledTimes(1); - expect(client.getOrganization).toHaveBeenCalledWith('tenantid'); + expect(client.getOrganization).toHaveBeenCalledWith( + 'tenantid', + undefined, + ); }); it('should read organization with custom transformer', async () => { @@ -563,7 +577,10 @@ describe('read microsoft graph', () => { expect(rootGroup).toEqual(undefined); expect(client.getOrganization).toHaveBeenCalledTimes(1); - expect(client.getOrganization).toHaveBeenCalledWith('tenantid'); + expect(client.getOrganization).toHaveBeenCalledWith( + 'tenantid', + undefined, + ); }); }); @@ -636,11 +653,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -716,11 +739,17 @@ describe('read microsoft graph', () => { }, 'advanced', undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -797,11 +826,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -871,11 +906,17 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); }); it('should read groups and their sub groups', async () => { @@ -976,14 +1017,25 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(2); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); - expect(client.getGroupMembers).toHaveBeenCalledWith('childgroupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'childgroupid', + { + top: 999, + }, + undefined, + undefined, + ); // TODO: Loading groups photos doesn't work right now as Microsoft Graph // doesn't allows this yet // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); @@ -1056,11 +1108,17 @@ describe('read microsoft graph', () => { }, undefined, '/groups/x/y', + undefined, ); expect(client.getGroupMembers).toHaveBeenCalledTimes(1); - expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { - top: 999, - }); + expect(client.getGroupMembers).toHaveBeenCalledWith( + 'groupid', + { + top: 999, + }, + undefined, + undefined, + ); }); }); @@ -1211,6 +1269,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1220,6 +1279,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1253,6 +1313,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1262,6 +1323,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1293,6 +1355,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1301,6 +1364,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1331,6 +1395,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); @@ -1369,6 +1434,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenNthCalledWith( 2, @@ -1378,6 +1444,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); }); @@ -1419,6 +1486,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); expect(client.getGroups).toHaveBeenCalledTimes(1); expect(client.getGroups).toHaveBeenCalledWith( @@ -1427,6 +1495,7 @@ describe('read microsoft graph', () => { }, undefined, undefined, + undefined, ); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 7202f70384..4a6c4163d1 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -53,6 +53,7 @@ export async function readMicrosoftGraphUsers( loadUserPhotos?: boolean; transformer?: UserTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; // With all relations empty @@ -66,6 +67,7 @@ export async function readMicrosoftGraphUsers( }, options.queryMode, options.userPath, + options.signal, ); return { @@ -93,6 +95,7 @@ export async function readMicrosoftGraphUsersInGroups( groupExpand?: string; transformer?: UserTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; // With all relations empty @@ -112,6 +115,7 @@ export async function readMicrosoftGraphUsersInGroups( }, options.queryMode, options.userGroupMemberPath, + options.signal, )) { // Process all groups in parallel, otherwise it can take quite some time userGroupMemberPromises.push( @@ -126,6 +130,7 @@ export async function readMicrosoftGraphUsersInGroups( top: PAGE_SIZE, }, options.queryMode, + options.signal, )) { userGroupMembers.set(user.id!, user); groupMemberCount++; @@ -161,12 +166,12 @@ export async function readMicrosoftGraphUsersInGroups( export async function readMicrosoftGraphOrganization( client: MicrosoftGraphClient, tenantId: string, - options?: { transformer?: OrganizationTransformer }, + options?: { transformer?: OrganizationTransformer; signal?: AbortSignal }, ): Promise<{ rootGroup?: GroupEntity; // With all relations empty }> { // For now we expect a single root organization - const organization = await client.getOrganization(tenantId); + const organization = await client.getOrganization(tenantId, options?.signal); const transformer = options?.transformer ?? defaultOrganizationTransformer; const rootGroup = await transformer(organization); @@ -186,6 +191,7 @@ export async function readMicrosoftGraphGroups( groupIncludeSubGroups?: boolean; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; + signal?: AbortSignal; }, ): Promise<{ groups: GroupEntity[]; // With all relations empty @@ -200,6 +206,7 @@ export async function readMicrosoftGraphGroups( const { rootGroup } = await readMicrosoftGraphOrganization(client, tenantId, { transformer: options?.organizationTransformer, + signal: options?.signal, }); if (rootGroup) { groupMember.set(rootGroup.metadata.name, new Set()); @@ -219,6 +226,7 @@ export async function readMicrosoftGraphGroups( }, options?.queryMode, options?.groupPath, + options?.signal, )) { // Process all groups in parallel, otherwise it can take quite some time promises.push( @@ -238,9 +246,14 @@ export async function readMicrosoftGraphGroups( return; } - for await (const member of client.getGroupMembers(group.id!, { - top: PAGE_SIZE, - })) { + for await (const member of client.getGroupMembers( + group.id!, + { + top: PAGE_SIZE, + }, + undefined, + options?.signal, + )) { if (!member.id) { continue; } @@ -261,6 +274,8 @@ export async function readMicrosoftGraphGroups( for await (const subMember of client.getGroupMembers( member.id!, { top: PAGE_SIZE }, + undefined, + options?.signal, )) { if (!subMember.id) { continue; @@ -425,6 +440,7 @@ export async function readMicrosoftGraphOrg( groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { let users: UserEntity[] = []; @@ -447,6 +463,7 @@ export async function readMicrosoftGraphOrg( loadUserPhotos: options.loadUserPhotos, transformer: options.userTransformer, logger: options.logger, + signal: options.signal, }, ); users = usersInGroups; @@ -460,6 +477,7 @@ export async function readMicrosoftGraphOrg( loadUserPhotos: options.loadUserPhotos, transformer: options.userTransformer, logger: options.logger, + signal: options.signal, }); users = usersWithFilter; } @@ -474,6 +492,7 @@ export async function readMicrosoftGraphOrg( groupIncludeSubGroups: options.groupIncludeSubGroups, groupTransformer: options.groupTransformer, organizationTransformer: options.organizationTransformer, + signal: options.signal, }); resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index 1d68f706bc..ddd91affda 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -255,6 +255,52 @@ describe('MicrosoftGraphOrgEntityProvider', () => { ); }); + it('should stop processing when AbortSignal is aborted', async () => { + jest.spyOn(logger, 'child').mockReturnValue(logger as any); + + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + const localTaskRunner = new PersistingTaskRunner(); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: localTaskRunner, + })[0]; + + await provider.connect(entityProviderConnection); + + const controller = new AbortController(); + controller.abort(); + + readMicrosoftGraphOrgMocked.mockImplementationOnce( + async (_client, _tenantId, options) => { + if (options.signal?.aborted) { + throw new DOMException('The operation was aborted', 'AbortError'); + } + return { users: [], groups: [] }; + }, + ); + + const taskDef = localTaskRunner.getTasks()[0]; + // Should resolve without throwing (the error is caught and logged internally) + await (taskDef.fn as (signal: AbortSignal) => Promise)( + controller.signal, + ); + + expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled(); + }); + it('fail without schedule and scheduler', () => { const config = new ConfigReader({ catalog: { diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index b223c18d6e..dca4fbef76 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -326,7 +326,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { * Runs one complete ingestion loop. Call this method regularly at some * appropriate cadence. */ - async read(options?: { logger?: LoggerService }) { + async read(options?: { logger?: LoggerService; signal?: AbortSignal }) { if (!this.connection) { throw new Error('Not initialized'); } @@ -361,6 +361,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer: this.options.userTransformer, organizationTransformer: this.options.organizationTransformer, logger: logger, + signal: options?.signal, }, ); @@ -386,7 +387,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { const id = `${this.getProviderName()}:refresh`; await taskRunner.run({ id, - fn: async () => { + fn: async (abortSignal: AbortSignal) => { const logger = this.options.logger.child({ class: MicrosoftGraphOrgEntityProvider.prototype.constructor.name, taskId: id, @@ -394,7 +395,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { }); try { - await this.read({ logger }); + await this.read({ logger, signal: abortSignal }); } catch (error) { logger.error( `${this.getProviderName()} refresh failed, ${error}`, From 97eaecf50f1d65789fd3ac2f05e9cc4b3e87b096 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Fri, 6 Mar 2026 23:00:21 -0600 Subject: [PATCH 2/3] chore: add changeset for AbortSignal fix in msgraph provider Signed-off-by: Lokesh Kaki --- .changeset/fluffy-colts-stop.md | 5 +++++ .../processors/MicrosoftGraphOrgEntityProvider.test.ts | 9 +++++++-- .../src/processors/MicrosoftGraphOrgEntityProvider.ts | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 .changeset/fluffy-colts-stop.md diff --git a/.changeset/fluffy-colts-stop.md b/.changeset/fluffy-colts-stop.md new file mode 100644 index 0000000000..9f6c9aba3d --- /dev/null +++ b/.changeset/fluffy-colts-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Fixed scheduler task remaining stuck in running state after pod termination by propagating AbortSignal into MicrosoftGraphOrgEntityProvider.read() diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index ddd91affda..786790bb2f 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -286,19 +286,24 @@ describe('MicrosoftGraphOrgEntityProvider', () => { readMicrosoftGraphOrgMocked.mockImplementationOnce( async (_client, _tenantId, options) => { if (options.signal?.aborted) { - throw new DOMException('The operation was aborted', 'AbortError'); + const error = new Error('The operation was aborted'); + error.name = 'AbortError'; + throw error; } return { users: [], groups: [] }; }, ); const taskDef = localTaskRunner.getTasks()[0]; - // Should resolve without throwing (the error is caught and logged internally) + // Should resolve without throwing (abort is caught and logged at debug level) await (taskDef.fn as (signal: AbortSignal) => Promise)( controller.signal, ); expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled(); + expect(logger.debug).toHaveBeenCalledWith( + expect.stringContaining('refresh aborted due to shutdown'), + ); }); it('fail without schedule and scheduler', () => { diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index dca4fbef76..a6f2ee78d7 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -397,6 +397,12 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { try { await this.read({ logger, signal: abortSignal }); } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + logger.debug( + `${this.getProviderName()} refresh aborted due to shutdown`, + ); + return; + } logger.error( `${this.getProviderName()} refresh failed, ${error}`, error, From df865b64a87d30ac4e9d936ce6189471212fc6ce Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Fri, 6 Mar 2026 23:46:59 -0600 Subject: [PATCH 3/3] chore(catalog): update api-report for AbortSignal additions in msgraph provider Signed-off-by: Lokesh Kaki --- .../report.api.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-msgraph/report.api.md b/plugins/catalog-backend-module-msgraph/report.api.md index c6687a638a..7799def397 100644 --- a/plugins/catalog-backend-module-msgraph/report.api.md +++ b/plugins/catalog-backend-module-msgraph/report.api.md @@ -78,6 +78,7 @@ export class MicrosoftGraphClient { groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; // (undocumented) getGroupPhoto(groupId: string, sizeId?: string): Promise; @@ -89,13 +90,18 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path?: string, + signal?: AbortSignal, ): AsyncIterable; getGroupUserMembers( groupId: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; - getOrganization(tenantId: string): Promise; + getOrganization( + tenantId: string, + signal?: AbortSignal, + ): Promise; // (undocumented) getUserPhoto(userId: string, sizeId?: string): Promise; getUserPhotoWithSizeLimit( @@ -106,21 +112,25 @@ export class MicrosoftGraphClient { query?: ODataQuery, queryMode?: 'basic' | 'advanced', path?: string, + signal?: AbortSignal, ): AsyncIterable; requestApi( path: string, query?: ODataQuery, headers?: Record, + signal?: AbortSignal, ): Promise; requestCollection( path: string, query?: ODataQuery, queryMode?: 'basic' | 'advanced', + signal?: AbortSignal, ): AsyncIterable; requestRaw( url: string, headers?: Record, retryCount?: number, + signal?: AbortSignal, ): Promise; } @@ -142,7 +152,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { options: MicrosoftGraphOrgEntityProviderOptions, ): MicrosoftGraphOrgEntityProvider[]; getProviderName(): string; - read(options?: { logger?: LoggerService }): Promise; + read(options?: { + logger?: LoggerService; + signal?: AbortSignal; + }): Promise; } // @public @deprecated @@ -304,6 +317,7 @@ export function readMicrosoftGraphOrg( groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; logger: LoggerService; + signal?: AbortSignal; }, ): Promise<{ users: UserEntity[];