Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pr-180.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wdio/browserstack-service": patch
---

- Fixed builds occasionally showing as "running" for hours after the run finished, caused by an unbounded network request during shutdown.
6 changes: 6 additions & 0 deletions packages/browserstack-service/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ export const STOP_BUILD_ATTEMPT_TIMEOUT_MS = 10000
export const STOP_BUILD_TOTAL_BUDGET_MS = 30000
export const STOP_BUILD_BACKOFF_BASE_MS = 1000

// SDK-7518: finalizeOrphanedRuns posts synthetic TestRunFinished events during shutdown,
// immediately before the build-stop call. Bound that POST so a hung connection cannot stall
// shutdown before the build is stopped (which leaves the build "running" until the server-side
// inactivity timeout). Sibling to STOP_BUILD_ATTEMPT_TIMEOUT_MS on the same shutdown path.
export const ORPHAN_FINALIZE_POST_TIMEOUT_MS = 10000

// API Endpoint constants
export const UPDATED_CLI_ENDPOINT = 'sdk/v1/update_cli'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs'

import type { TestData, UploadType } from '../types.js'
import { batchAndPostEvents } from '../util.js'
import { DATA_BATCH_ENDPOINT } from '../constants.js'
import { DATA_BATCH_ENDPOINT, ORPHAN_FINALIZE_POST_TIMEOUT_MS } from '../constants.js'
import { BStackLogger } from '../bstackLogger.js'

/**
Expand Down Expand Up @@ -94,7 +94,7 @@ export async function finalizeOrphanedRuns(): Promise<number> {
}
return { event_type: 'TestRunFinished', test_run: finishedRun }
})
await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'ORPHANED_TEST_RUN_FINALIZATION', events)
await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'ORPHANED_TEST_RUN_FINALIZATION', events, ORPHAN_FINALIZE_POST_TIMEOUT_MS)
BStackLogger.info(`Finalized ${events.length} orphaned test/hook run(s) left behind by an interrupted run`)
return events.length
} catch (e) {
Expand Down
13 changes: 11 additions & 2 deletions packages/browserstack-service/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ export function shouldAddServiceVersion(config: Options.Testrunner, testObservab
return true
}

export async function batchAndPostEvents (eventUrl: string, kind: string, data: UploadType[]) {
export async function batchAndPostEvents (eventUrl: string, kind: string, data: UploadType[], timeoutMs?: number) {
if (!process.env[TESTOPS_BUILD_COMPLETED_ENV]) {
throw new Error('Build not completed yet')
}
Expand All @@ -1337,6 +1337,12 @@ export async function batchAndPostEvents (eventUrl: string, kind: string, data:
throw new Error('Missing authentication Token')
}

// SDK-7518: an optional per-call timeout. When set (by shutdown-path callers such as
// finalizeOrphanedRuns), bound the fetch with an AbortController so a hung connection cannot
// stall shutdown before the build-stop call. Left unset by normal in-run callers, whose
// behaviour is unchanged.
const controller = timeoutMs ? new AbortController() : undefined
const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : undefined
try {
const url = `${APIUtils.DATA_ENDPOINT}/${eventUrl}`
const response = await fetch(url, {
Expand All @@ -1345,7 +1351,8 @@ export async function batchAndPostEvents (eventUrl: string, kind: string, data:
...DEFAULT_REQUEST_CONFIG.headers,
'Authorization': `Bearer ${jwtToken}`
},
body: JSON.stringify(data)
body: JSON.stringify(data),
signal: controller?.signal
})
// read as text first: error responses (401/5xx) and empty bodies are not JSON, and a blind
// response.json() surfaced them as a misleading "Unexpected end of JSON input"
Expand All @@ -1357,6 +1364,8 @@ export async function batchAndPostEvents (eventUrl: string, kind: string, data:
} catch (error) {
BStackLogger.debug(`[${kind}] EXCEPTION IN ${kind} REQUEST TO TEST REPORTING AND ANALYTICS : ${error}`)
throw new Error('Exception in request ' + error)
} finally {
if (timeoutId) { clearTimeout(timeoutId) }
}
}

Expand Down
Loading