Skip to content
This repository has been archived by the owner on Jul 3, 2019. It is now read-only.

Commit

Permalink
fix(perf): refactor content.read to avoid lstats
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Aug 1, 2018
1 parent 1950490 commit c5ac10e
Showing 1 changed file with 45 additions and 46 deletions.
91 changes: 45 additions & 46 deletions lib/content/read.js
Expand Up @@ -10,7 +10,8 @@ const pipe = BB.promisify(require('mississippi').pipe)
const ssri = require('ssri')
const Y = require('../util/y.js')

BB.promisifyAll(fs)
const lstatAsync = BB.promisify(fs.lstat)
const readFileAsync = BB.promisify(fs.readFile)

const ReadOpts = figgyPudding({
size: {}
Expand All @@ -19,10 +20,8 @@ const ReadOpts = figgyPudding({
module.exports = read
function read (cache, integrity, opts) {
opts = ReadOpts(opts)
return pickContentSri(cache, integrity).then(content => {
const sri = content.sri
const cpath = contentPath(cache, sri)
return fs.readFileAsync(cpath, null).then(data => {
return withContentSri(cache, integrity, (cpath, sri) => {
return readFileAsync(cpath, null).then(data => {
if (typeof opts.size === 'number' && opts.size !== data.length) {
throw sizeError(opts.size, data.length)
} else if (ssri.checkData(data, sri)) {
Expand All @@ -39,12 +38,11 @@ module.exports.readStream = readStream
function readStream (cache, integrity, opts) {
opts = ReadOpts(opts)
const stream = new PassThrough()
pickContentSri(
cache, integrity
).then(content => {
const sri = content.sri
withContentSri(cache, integrity, (cpath, sri) => {
return lstatAsync(cpath).then(stat => ({cpath, sri, stat}))
}).then(({cpath, sri, stat}) => {
return pipe(
fs.createReadStream(contentPath(cache, sri)),
fs.createReadStream(cpath),
ssri.integrityStream({
integrity: sri,
size: opts.size
Expand All @@ -57,60 +55,61 @@ function readStream (cache, integrity, opts) {
return stream
}

let copyFileAsync
if (fs.copyFile) {
module.exports.copy = copy
copyFileAsync = BB.promisify(fs.copyFile)
}
function copy (cache, integrity, dest, opts) {
opts = ReadOpts(opts)
return pickContentSri(cache, integrity).then(content => {
const sri = content.sri
const cpath = contentPath(cache, sri)
return fs.copyFileAsync(cpath, dest).then(() => content.size)
return withContentSri(cache, integrity, (cpath, sri) => {
return copyFileAsync(cpath, dest)
})
}

module.exports.hasContent = hasContent
function hasContent (cache, integrity) {
if (!integrity) { return BB.resolve(false) }
return pickContentSri(cache, integrity)
.catch({code: 'ENOENT'}, () => false)
.catch({code: 'EPERM'}, err => {
return withContentSri(cache, integrity, (cpath, sri) => {
return lstatAsync(cpath).then(stat => ({size: stat.size, sri}))
}).catch(err => {
if (err.code === 'ENOENT') { return false }
if (err.code === 'EPERM') {
if (process.platform !== 'win32') {
throw err
} else {
return false
}
}).then(content => {
if (!content.sri) return false
return ({ sri: content.sri, size: content.stat.size })
})
}
})
}

module.exports._pickContentSri = pickContentSri
function pickContentSri (cache, integrity) {
const sri = ssri.parse(integrity)
// If `integrity` has multiple entries, pick the first digest
// with available local data.
const algo = sri.pickAlgorithm()
const digests = sri[algo]
if (digests.length <= 1) {
const cpath = contentPath(cache, digests[0])
return fs.lstatAsync(cpath).then(stat => ({ sri: digests[0], stat }))
} else {
return BB.any(sri[sri.pickAlgorithm()].map(meta => {
return pickContentSri(cache, meta)
}))
.catch(err => {
if ([].some.call(err, e => e.code === 'ENOENT')) {
throw Object.assign(
new Error('No matching content found for ' + sri.toString()),
{code: 'ENOENT'}
)
} else {
throw err[0]
}
})
}
function withContentSri (cache, integrity, fn) {
return BB.try(() => {
const sri = ssri.parse(integrity)
// If `integrity` has multiple entries, pick the first digest
// with available local data.
const algo = sri.pickAlgorithm()
const digests = sri[algo]
if (digests.length <= 1) {
const cpath = contentPath(cache, digests[0])
return fn(cpath, digests[0])
} else {
return BB.any(sri[sri.pickAlgorithm()].map(meta => {
return withContentSri(cache, meta, fn)
}, {concurrency: 1}))
.catch(err => {
if ([].some.call(err, e => e.code === 'ENOENT')) {
throw Object.assign(
new Error('No matching content found for ' + sri.toString()),
{code: 'ENOENT'}
)
} else {
throw err[0]
}
})
}
})
}

function sizeError (expected, found) {
Expand Down

0 comments on commit c5ac10e

Please sign in to comment.