Skip to content

Commit a907f6f

Browse files
committed
Migrate to registry's downloadWithLock for binary downloads
Replace custom download logic with @socketsecurity/registry/lib/download-lock: Benefits: - Cross-process download locking prevents race conditions - Automatic retry logic with exponential backoff (3 retries) - Configurable timeouts for large files - Stale lock detection and cleanup - Better reliability in CI/CD environments with parallel builds Files updated: - src/utils/dlx-binary.mts: Binary downloads now use downloadWithLock - src/utils/python-standalone.mts: Python runtime downloads use downloadWithLock Configuration: - dlx-binary: 2min lock timeout, 5min download timeout - python-standalone: 3min lock timeout, 10min download timeout (larger files) Note: Requires @socketsecurity/registry@1.4.0+ for download-lock module
1 parent 671ee81 commit a907f6f

File tree

2 files changed

+34
-31
lines changed

2 files changed

+34
-31
lines changed

src/utils/dlx-binary.mts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ import { existsSync, promises as fs } from 'node:fs'
2626
import os from 'node:os'
2727
import path from 'node:path'
2828

29+
import { downloadWithLock } from '@socketsecurity/registry/lib/download-lock'
2930
import { readJson, remove } from '@socketsecurity/registry/lib/fs'
3031
import { getSocketDlxDir } from '@socketsecurity/registry/lib/paths'
3132
import { spawn } from '@socketsecurity/registry/lib/spawn'
3233

3334
import constants from '../constants.mts'
3435
import { InputError } from './errors.mts'
35-
import { httpRequest } from './http.mts'
3636

3737
import type {
3838
SpawnExtra,
@@ -111,39 +111,33 @@ async function isCacheValid(
111111
}
112112

113113
/**
114-
* Download a file from a URL with integrity checking.
114+
* Download a file from a URL with integrity checking and download locking.
115+
* Uses registry's downloadWithLock to prevent concurrent downloads.
115116
*/
116117
async function downloadBinary(
117118
url: string,
118119
destPath: string,
119120
checksum?: string,
120121
): Promise<string> {
121-
const result = await httpRequest(url)
122-
123-
if (!result.ok) {
124-
throw new InputError(`Failed to download binary: ${result.message}`)
125-
}
126-
127-
const response = result.data!
128-
129-
if (!response.ok) {
130-
throw new InputError(
131-
`Failed to download binary: ${response.status} ${response.statusText}`,
132-
)
133-
}
134-
135-
// Create a temporary file first.
122+
// Create a temporary file first for integrity checking.
136123
const tempPath = `${destPath}.download`
137-
const hasher = createHash('sha256')
138124

139125
try {
140126
// Ensure directory exists.
141127
await fs.mkdir(path.dirname(destPath), { recursive: true })
142128

143-
// Get the response as a buffer and compute hash.
144-
const buffer = response.body
145-
146-
// Compute hash.
129+
// Download with locking and automatic retries.
130+
// This prevents concurrent downloads and provides retry logic.
131+
await downloadWithLock(url, tempPath, {
132+
lockTimeout: 120_000, // Wait up to 2 minutes for concurrent downloads
133+
retries: 3, // Retry up to 3 times with exponential backoff
134+
retryDelay: 1000, // Start with 1 second delay
135+
timeout: 300_000, // 5 minute timeout per attempt
136+
})
137+
138+
// Read file for checksum verification.
139+
const buffer = await fs.readFile(tempPath)
140+
const hasher = createHash('sha256')
147141
hasher.update(buffer)
148142
const actualChecksum = hasher.digest('hex')
149143

@@ -154,9 +148,6 @@ async function downloadBinary(
154148
)
155149
}
156150

157-
// Write to temp file.
158-
await fs.writeFile(tempPath, buffer)
159-
160151
// Make executable on POSIX systems.
161152
if (os.platform() !== 'win32') {
162153
await fs.chmod(tempPath, 0o755)
@@ -173,7 +164,9 @@ async function downloadBinary(
173164
} catch {
174165
// Ignore cleanup errors.
175166
}
176-
throw error
167+
throw error instanceof Error
168+
? error
169+
: new InputError(`Failed to download binary: ${String(error)}`)
177170
}
178171
}
179172

src/utils/python-standalone.mts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ import path from 'node:path'
4646
import semver from 'semver'
4747

4848
import { whichBin } from '@socketsecurity/registry/lib/bin'
49+
import { downloadWithLock } from '@socketsecurity/registry/lib/download-lock'
4950
import { remove } from '@socketsecurity/registry/lib/fs'
5051
import { spawn } from '@socketsecurity/registry/lib/spawn'
5152

5253
import constants from '../constants.mts'
5354
import { getDlxCachePath } from './dlx-binary.mts'
5455
import { InputError, getErrorCause } from './errors.mts'
55-
import { httpDownload } from './http.mts'
5656

5757
import type { CResult } from '../types.mts'
5858

@@ -188,6 +188,7 @@ export async function checkSystemPython(): Promise<string | null> {
188188

189189
/**
190190
* Download and extract Python from python-build-standalone.
191+
* Uses downloadWithLock to prevent concurrent downloads.
191192
*/
192193
async function downloadPython(pythonDir: string): Promise<void> {
193194
const url = getPythonStandaloneUrl()
@@ -196,10 +197,19 @@ async function downloadPython(pythonDir: string): Promise<void> {
196197
// Ensure directory exists
197198
await fs.mkdir(pythonDir, { recursive: true })
198199

199-
// Download with Node's native http module
200-
const result = await httpDownload(url, tarballPath)
201-
if (!result.ok) {
202-
throw new InputError(`Failed to download Python: ${result.message}`)
200+
// Download with locking and automatic retries
201+
// This prevents concurrent downloads and provides retry logic
202+
try {
203+
await downloadWithLock(url, tarballPath, {
204+
lockTimeout: 180_000, // Wait up to 3 minutes for concurrent downloads
205+
retries: 3, // Retry up to 3 times with exponential backoff
206+
retryDelay: 1000, // Start with 1 second delay
207+
timeout: 600_000, // 10 minute timeout per attempt (large file)
208+
})
209+
} catch (error) {
210+
throw new InputError(
211+
`Failed to download Python: ${error instanceof Error ? error.message : String(error)}`,
212+
)
203213
}
204214

205215
// Extract using system tar command

0 commit comments

Comments
 (0)