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'
|
} from '../../configuration'
|
||||||
import {saveDeprecationState} from '../../deprecation-collector'
|
import {saveDeprecationState} from '../../deprecation-collector'
|
||||||
import {handleMainActionError} from '../../errors'
|
import {handleMainActionError} from '../../errors'
|
||||||
|
import {forceExit} from '../../force-exit'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main entry point for the action, called by Github Actions for the step.
|
* 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.
|
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||||
process.exit()
|
await forceExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as setupGradle from '../../setup-gradle'
|
|||||||
|
|
||||||
import {CacheConfig, SummaryConfig} from '../../configuration'
|
import {CacheConfig, SummaryConfig} from '../../configuration'
|
||||||
import {handlePostActionError} from '../../errors'
|
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
|
// 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
|
// @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.
|
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||||
process.exit()
|
await forceExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '../../configuration'
|
} from '../../configuration'
|
||||||
import {failOnUseOfRemovedFeature, saveDeprecationState} from '../../deprecation-collector'
|
import {failOnUseOfRemovedFeature, saveDeprecationState} from '../../deprecation-collector'
|
||||||
import {handleMainActionError} from '../../errors'
|
import {handleMainActionError} from '../../errors'
|
||||||
|
import {forceExit} from '../../force-exit'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main entry point for the action, called by Github Actions for the step.
|
* 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.
|
// Explicit process.exit() to prevent waiting for hanging promises.
|
||||||
process.exit()
|
await forceExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as dependencyGraph from '../../dependency-graph'
|
|||||||
import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../../configuration'
|
import {CacheConfig, DependencyGraphConfig, SummaryConfig} from '../../configuration'
|
||||||
import {handlePostActionError} from '../../errors'
|
import {handlePostActionError} from '../../errors'
|
||||||
import {emitDeprecationWarnings, restoreDeprecationState} from '../../deprecation-collector'
|
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
|
// 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
|
// @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.
|
// Explicit process.exit() to prevent waiting for promises left hanging by `@actions/cache` on save.
|
||||||
process.exit()
|
await forceExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
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