Skip to content

Commit

Permalink
fix: occasional concurrency issue when fetching external tools
Browse files Browse the repository at this point in the history
  • Loading branch information
edvald authored and thsig committed Mar 12, 2019
1 parent 0685a9b commit 9d61d71
Showing 1 changed file with 31 additions and 28 deletions.
59 changes: 31 additions & 28 deletions garden-service/src/util/ext-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Extract } from "unzipper"
import { createHash } from "crypto"
import * as uuid from "uuid"
import * as spawn from "cross-spawn"
const AsyncLock = require("async-lock")

const globalGardenPath = join(homedir(), ".garden")
const toolsPath = join(globalGardenPath, "tools")
Expand Down Expand Up @@ -68,6 +69,7 @@ export class BinaryCmd extends Cmd {
name: string
spec: BinarySpec

private lock: any
private toolPath: string
private versionDirname: string
private versionPath: string
Expand All @@ -88,6 +90,8 @@ export class BinaryCmd extends Cmd {
)
}

this.lock = new AsyncLock()

this.name = spec.name
this.spec = platformSpec
this.toolPath = join(toolsPath, this.name)
Expand All @@ -102,43 +106,42 @@ export class BinaryCmd extends Cmd {
}

private async download(log: LogEntry) {
// TODO: use lockfile to avoid multiple downloads of the same thing
// (we avoid a race condition by downloading to a temporary path, so it's more about efficiency)

if (await pathExists(this.executablePath)) {
return
}
return this.lock.acquire("download", async () => {
if (await pathExists(this.executablePath)) {
return
}

const tmpPath = join(this.toolPath, this.versionDirname + "." + uuid.v4().substr(0, 8))
const tmpExecutable = join(tmpPath, ...this.executableSubpath)
const tmpPath = join(this.toolPath, this.versionDirname + "." + uuid.v4().substr(0, 8))
const tmpExecutable = join(tmpPath, ...this.executableSubpath)

const logEntry = log.verbose(`Fetching ${this.name}...`)
const debug = logEntry.debug(`Downloading ${this.spec.url}...`)
const logEntry = log.verbose(`Fetching ${this.name}...`)
const debug = logEntry.debug(`Downloading ${this.spec.url}...`)

await ensureDir(tmpPath)
await ensureDir(tmpPath)

try {
await this.fetch(tmpPath, log)
try {
await this.fetch(tmpPath, log)

if (!(await pathExists(tmpExecutable))) {
throw new ConfigurationError(
`Archive ${this.spec.url} does not contain a file at ${join(...this.spec.extract!.executablePath)}`,
{ name: this.name, spec: this.spec },
)
}
if (!(await pathExists(tmpExecutable))) {
throw new ConfigurationError(
`Archive ${this.spec.url} does not contain a file at ${join(...this.spec.extract!.executablePath)}`,
{ name: this.name, spec: this.spec },
)
}

await chmod(tmpExecutable, 0o755)
await move(tmpPath, this.versionPath, { overwrite: true })
await chmod(tmpExecutable, 0o755)
await move(tmpPath, this.versionPath, { overwrite: true })

} finally {
// make sure tmp path is cleared after errors
if (await pathExists(tmpPath)) {
await remove(tmpPath)
} finally {
// make sure tmp path is cleared after errors
if (await pathExists(tmpPath)) {
await remove(tmpPath)
}
}
}

debug && debug.setSuccess("Done")
logEntry.setSuccess(`Fetched ${this.name}`)
debug && debug.setSuccess("Done")
logEntry.setSuccess(`Fetched ${this.name}`)
})
}

async exec({ cwd, args, log, timeout }: ExecParams) {
Expand Down

0 comments on commit 9d61d71

Please sign in to comment.