mirror of
https://github.com/gradle/actions.git
synced 2026-03-22 12:05:48 +08:00
Avoid windows shutdown bug
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from '../../configuration'
|
||||
import {saveDeprecationState} from '../../deprecation-collector'
|
||||
import {handleMainActionError} from '../../errors'
|
||||
import {forceExit} from '../../force-exit'
|
||||
|
||||
/**
|
||||
* The main entry point for the action, called by Github Actions for the step.
|
||||
@@ -67,7 +68,7 @@ export async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||
process.exit()
|
||||
await forceExit()
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as setupGradle from '../../setup-gradle'
|
||||
|
||||
import {CacheConfig, SummaryConfig} from '../../configuration'
|
||||
import {handlePostActionError} from '../../errors'
|
||||
import {forceExit} from '../../force-exit'
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
@@ -19,7 +20,7 @@ export async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||
process.exit()
|
||||
await forceExit()
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '../../configuration'
|
||||
import {failOnUseOfRemovedFeature, saveDeprecationState} from '../../deprecation-collector'
|
||||
import {handleMainActionError} from '../../errors'
|
||||
import {forceExit} from '../../force-exit'
|
||||
|
||||
/**
|
||||
* The main entry point for the action, called by Github Actions for the step.
|
||||
@@ -42,7 +43,7 @@ export async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||
process.exit()
|
||||
await forceExit()
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as dependencyGraph from '../../dependency-graph'
|
||||
import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../../configuration'
|
||||
import {handlePostActionError} from '../../errors'
|
||||
import {emitDeprecationWarnings, restoreDeprecationState} from '../../deprecation-collector'
|
||||
import {forceExit} from '../../force-exit'
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
@@ -27,7 +28,7 @@ export async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||
process.exit()
|
||||
await forceExit()
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
14
sources/src/force-exit.ts
Normal file
14
sources/src/force-exit.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
const WINDOWS_EXIT_DELAY_MS = 50
|
||||
|
||||
export function getForcedExitDelayMs(platform: NodeJS.Platform = process.platform): number {
|
||||
return platform === 'win32' ? WINDOWS_EXIT_DELAY_MS : 0
|
||||
}
|
||||
|
||||
export async function forceExit(platform: NodeJS.Platform = process.platform): Promise<never> {
|
||||
const exitDelayMs = getForcedExitDelayMs(platform)
|
||||
if (exitDelayMs > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, exitDelayMs))
|
||||
}
|
||||
|
||||
return process.exit()
|
||||
}
|
||||
39
sources/test/jest/force-exit.test.ts
Normal file
39
sources/test/jest/force-exit.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {afterEach, describe, expect, it, jest} from '@jest/globals'
|
||||
|
||||
import {forceExit, getForcedExitDelayMs} from '../../src/force-exit'
|
||||
|
||||
describe('forceExit', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('adds a short delay on Windows before exiting', async () => {
|
||||
jest.useFakeTimers()
|
||||
|
||||
const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
|
||||
|
||||
const exitPromise = forceExit('win32')
|
||||
await jest.advanceTimersByTimeAsync(49)
|
||||
|
||||
expect(exitSpy).not.toHaveBeenCalled()
|
||||
|
||||
await jest.advanceTimersByTimeAsync(1)
|
||||
await expect(exitPromise).resolves.toBeUndefined()
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('exits immediately on non-Windows platforms', async () => {
|
||||
const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined) as never)
|
||||
|
||||
await expect(forceExit('linux')).resolves.toBeUndefined()
|
||||
expect(exitSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('only delays on Windows', () => {
|
||||
expect(getForcedExitDelayMs('win32')).toBe(50)
|
||||
expect(getForcedExitDelayMs('linux')).toBe(0)
|
||||
expect(getForcedExitDelayMs('darwin')).toBe(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user