Skip to content

Commit ede05b9

Browse files
committed
Make concurrency change opt-in, but can only go lower
1 parent f3c12d5 commit ede05b9

File tree

2 files changed

+81
-4
lines changed

2 files changed

+81
-4
lines changed

packages/artifact/__tests__/config.test.ts

Lines changed: 45 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()
@@ -35,14 +41,53 @@ describe('uploadChunkTimeoutEnv', () => {
3541
it('should return default 300000 when no env set', () => {
3642
expect(config.getUploadChunkTimeout()).toBe(300000)
3743
})
44+
3845
it('should return value set in ACTIONS_UPLOAD_TIMEOUT_MS', () => {
3946
process.env.ACTIONS_UPLOAD_TIMEOUT_MS = '150000'
4047
expect(config.getUploadChunkTimeout()).toBe(150000)
4148
})
49+
4250
it('should throw if value set in ACTIONS_UPLOAD_TIMEOUT_MS is invalid', () => {
4351
process.env.ACTIONS_UPLOAD_TIMEOUT_MS = 'abc'
4452
expect(() => {
4553
config.getUploadChunkTimeout()
4654
}).toThrow()
4755
})
4856
})
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_UPLOAD_CONCURRENCY is set', () => {
75+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
76+
process.env.ACTIONS_UPLOAD_CONCURRENCY = '10'
77+
expect(config.getConcurrency()).toBe(10)
78+
})
79+
80+
it('should throw with invalid value of ACTIONS_UPLOAD_CONCURRENCY', () => {
81+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
82+
process.env.ACTIONS_UPLOAD_CONCURRENCY = 'abc'
83+
expect(() => {
84+
config.getConcurrency()
85+
}).toThrow()
86+
})
87+
88+
it('cannot go over currency cap when override value is greater', () => {
89+
;(os.cpus as jest.Mock).mockReturnValue(new Array(4))
90+
process.env.ACTIONS_UPLOAD_CONCURRENCY = '40'
91+
expect(config.getConcurrency()).toBe(32)
92+
})
93+
})

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

Lines changed: 36 additions & 4 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
@@ -44,20 +45,51 @@ export function getGitHubWorkspaceDir(): string {
4445
return ghWorkspaceDir
4546
}
4647

47-
// From testing, setting this value to 10 yielded best results in terms of reliability and there are no impact on performance either
48+
// Mimics behavior of azcopy: https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-optimize
49+
// If your machine has fewer than 5 CPUs, then the value of this variable is set to 32.
50+
// 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_UPLOAD_CONCURRENCY variable.
4852
export function getConcurrency(): number {
49-
return 10
53+
const numCPUs = os.cpus().length
54+
let concurrencyCap = 32
55+
56+
if (numCPUs > 4) {
57+
const concurrency = 16 * numCPUs
58+
concurrencyCap = concurrency > 300 ? 300 : concurrency
59+
}
60+
61+
const concurrencyOverride = process.env['ACTIONS_UPLOAD_CONCURRENCY']
62+
if (concurrencyOverride) {
63+
const concurrency = parseInt(concurrencyOverride)
64+
if (isNaN(concurrency)) {
65+
throw new Error(
66+
'Invalid value set for ACTIONS_UPLOAD_CONCURRENCY env variable'
67+
)
68+
}
69+
70+
if (concurrency < concurrencyCap) {
71+
return concurrency
72+
}
73+
74+
info(
75+
`ACTIONS_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
5080
}
5181

5282
export function getUploadChunkTimeout(): number {
53-
const timeoutVar = process.env['ACTIONS_UPLOAD_TIMEOUT_MS']
83+
const timeoutVar = process.env['ACTIONS_UPLOAD_TIMEOUT_MS']
5484
if (!timeoutVar) {
5585
return 300000 // 5 minutes
5686
}
5787

5888
const timeout = parseInt(timeoutVar)
5989
if (isNaN(timeout)) {
60-
throw new Error('Invalid value set for ACTIONS_UPLOAD_TIMEOUT_MS env variable')
90+
throw new Error(
91+
'Invalid value set for ACTIONS_UPLOAD_TIMEOUT_MS env variable'
92+
)
6193
}
6294

6395
return timeout

0 commit comments

Comments
 (0)