Skip to content

Commit 16ef144

Browse files
authored
Merge pull request #1928 from actions/yacaovsnc/artifact_upload_concurrency_and_timeout
Make both upload concurrency and timeout settings configurable with env variables.
2 parents adb9c4a + e554093 commit 16ef144

File tree

2 files changed

+107
-5
lines changed

2 files changed

+107
-5
lines changed

packages/artifact/__tests__/config.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import * as config from '../src/internal/shared/config'
2+
import os from 'os'
3+
4+
// Mock the 'os' module
5+
jest.mock('os', () => ({
6+
cpus: jest.fn()
7+
}))
28

39
beforeEach(() => {
410
jest.resetModules()
@@ -30,3 +36,66 @@ describe('isGhes', () => {
3036
expect(config.isGhes()).toBe(true)
3137
})
3238
})
39+
40+
describe('uploadChunkTimeoutEnv', () => {
41+
it('should return default 300000 when no env set', () => {
42+
expect(config.getUploadChunkTimeout()).toBe(300000)
43+
})
44+
45+
it('should return value set in ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS', () => {
46+
process.env.ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS = '150000'
47+
expect(config.getUploadChunkTimeout()).toBe(150000)
48+
})
49+
50+
it('should throw if value set in ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS is invalid', () => {
51+
process.env.ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS = 'abc'
52+
expect(() => {
53+
config.getUploadChunkTimeout()
54+
}).toThrow()
55+
})
56+
})
57+
58+
describe('uploadConcurrencyEnv', () => {
59+
it('should return default 32 when cpu num is <= 4', () => {
60+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
61+
expect(config.getConcurrency()).toBe(32)
62+
})
63+
64+
it('should return 16 * num of cpu when cpu num is > 4', () => {
65+
;(os.cpus as jest.Mock).mockReturnValue(new Array(6))
66+
expect(config.getConcurrency()).toBe(96)
67+
})
68+
69+
it('should return up to 300 max value', () => {
70+
;(os.cpus as jest.Mock).mockReturnValue(new Array(32))
71+
expect(config.getConcurrency()).toBe(300)
72+
})
73+
74+
it('should return override value when ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY is set', () => {
75+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
76+
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '10'
77+
expect(config.getConcurrency()).toBe(10)
78+
})
79+
80+
it('should throw with invalid value of ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY', () => {
81+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
82+
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = 'abc'
83+
expect(() => {
84+
config.getConcurrency()
85+
}).toThrow()
86+
})
87+
88+
it('should throw if ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY is < 1', () => {
89+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
90+
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '0'
91+
expect(() => {
92+
config.getConcurrency()
93+
}).toThrow()
94+
})
95+
96+
it('cannot go over currency cap when override value is greater', () => {
97+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
98+
process.env.ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY = '40'
99+
expect(config.getConcurrency()).toBe(32)
100+
})
101+
})

packages/artifact/src/internal/shared/config.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os from 'os'
2+
import {info} from '@actions/core'
23

34
// Used for controlling the highWaterMark value of the zip that is being streamed
45
// The same value is used as the chunk size that is use during upload to blob storage
@@ -47,17 +48,49 @@ export function getGitHubWorkspaceDir(): string {
4748
// Mimics behavior of azcopy: https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-optimize
4849
// If your machine has fewer than 5 CPUs, then the value of this variable is set to 32.
4950
// Otherwise, the default value is equal to 16 multiplied by the number of CPUs. The maximum value of this variable is 300.
51+
// This value can be lowered with ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY variable.
5052
export function getConcurrency(): number {
5153
const numCPUs = os.cpus().length
54+
let concurrencyCap = 32
5255

53-
if (numCPUs <= 4) {
54-
return 32
56+
if (numCPUs > 4) {
57+
const concurrency = 16 * numCPUs
58+
concurrencyCap = concurrency > 300 ? 300 : concurrency
5559
}
5660

57-
const concurrency = 16 * numCPUs
58-
return concurrency > 300 ? 300 : concurrency
61+
const concurrencyOverride = process.env['ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY']
62+
if (concurrencyOverride) {
63+
const concurrency = parseInt(concurrencyOverride)
64+
if (isNaN(concurrency) || concurrency < 1) {
65+
throw new Error(
66+
'Invalid value set for ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY env variable'
67+
)
68+
}
69+
70+
if (concurrency < concurrencyCap) {
71+
return concurrency
72+
}
73+
74+
info(
75+
`ACTIONS_ARTIFACT_UPLOAD_CONCURRENCY is higher than the cap of ${concurrencyCap} based on the number of cpus. Lowering it to the cap.`
76+
)
77+
}
78+
79+
return concurrencyCap
5980
}
6081

6182
export function getUploadChunkTimeout(): number {
62-
return 300_000 // 5 minutes
83+
const timeoutVar = process.env['ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS']
84+
if (!timeoutVar) {
85+
return 300000 // 5 minutes
86+
}
87+
88+
const timeout = parseInt(timeoutVar)
89+
if (isNaN(timeout)) {
90+
throw new Error(
91+
'Invalid value set for ACTIONS_ARTIFACT_UPLOAD_TIMEOUT_MS env variable'
92+
)
93+
}
94+
95+
return timeout
6396
}

0 commit comments

Comments
 (0)