mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
Merge pull request #2183 from resin-io/fix-ui-writer-errors
fix(writer): Fix erronous event handling in write pipeline
This commit is contained in:
commit
c3c15e222d
@ -102,17 +102,6 @@ permissions.isElevated().then((elevated) => {
|
|||||||
checksumAlgorithms: options.check ? [ 'sha512' ] : []
|
checksumAlgorithms: options.check ? [ 'sha512' ] : []
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Error handler
|
|
||||||
* @param {Error} error - error
|
|
||||||
* @private
|
|
||||||
* @example
|
|
||||||
* writer.on('error', onError)
|
|
||||||
*/
|
|
||||||
const onError = function (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Finish handler
|
* @summary Finish handler
|
||||||
* @private
|
* @private
|
||||||
@ -124,7 +113,7 @@ permissions.isElevated().then((elevated) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.on('progress', onProgress)
|
writer.on('progress', onProgress)
|
||||||
writer.on('error', onError)
|
writer.on('error', reject)
|
||||||
writer.on('finish', onFinish)
|
writer.on('finish', onFinish)
|
||||||
|
|
||||||
// NOTE: Drive can be (String|Array)
|
// NOTE: Drive can be (String|Array)
|
||||||
|
@ -126,6 +126,10 @@ exports.performWrite = (image, drives, onProgress) => {
|
|||||||
console.log(message)
|
console.log(message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipc.server.on('fail', (error) => {
|
||||||
|
console.log('Fail:', error)
|
||||||
|
})
|
||||||
|
|
||||||
const flashResults = {}
|
const flashResults = {}
|
||||||
ipc.server.on('done', (results) => {
|
ipc.server.on('done', (results) => {
|
||||||
_.merge(flashResults, results)
|
_.merge(flashResults, results)
|
||||||
|
@ -61,6 +61,8 @@ exports.fromFlashState = (state) => {
|
|||||||
return `${state.percentage}% Flashing`
|
return `${state.percentage}% Flashing`
|
||||||
} else if (isValidating) {
|
} else if (isValidating) {
|
||||||
return `${state.percentage}% Validating`
|
return `${state.percentage}% Validating`
|
||||||
|
} else if (!isFlashing && !isValidating) {
|
||||||
|
return 'Failed'
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Invalid state: ${JSON.stringify(state)}`)
|
throw new Error(`Invalid state: ${JSON.stringify(state)}`)
|
||||||
|
@ -154,6 +154,27 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
|||||||
ipc.of[IPC_SERVER_ID].emit('error', error)
|
ipc.of[IPC_SERVER_ID].emit('error', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Failure handler (non-fatal errors)
|
||||||
|
* @param {Object} event - event data (error & device)
|
||||||
|
* @example
|
||||||
|
* writer.on('fail', onFail)
|
||||||
|
*/
|
||||||
|
const onFail = (event) => {
|
||||||
|
ipc.of[IPC_SERVER_ID].emit('fail', {
|
||||||
|
device: event.device,
|
||||||
|
error: {
|
||||||
|
name: event.error.name,
|
||||||
|
message: event.error.message,
|
||||||
|
code: event.error.code,
|
||||||
|
syscall: event.error.syscall,
|
||||||
|
errno: event.error.errno,
|
||||||
|
stack: event.error.stack,
|
||||||
|
stdout: event.error.stdout
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const writer = new ImageWriter({
|
const writer = new ImageWriter({
|
||||||
verify: options.validateWriteOnSuccess,
|
verify: options.validateWriteOnSuccess,
|
||||||
unmountOnSuccess: options.unmountOnSuccess,
|
unmountOnSuccess: options.unmountOnSuccess,
|
||||||
@ -161,6 +182,7 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
writer.on('error', onError)
|
writer.on('error', onError)
|
||||||
|
writer.on('fail', onFail)
|
||||||
writer.on('progress', onProgress)
|
writer.on('progress', onProgress)
|
||||||
writer.on('finish', onFinish)
|
writer.on('finish', onFinish)
|
||||||
|
|
||||||
|
@ -217,6 +217,7 @@ class ImageWriter extends EventEmitter {
|
|||||||
|
|
||||||
mountutils.unmountDisk(destination.device.device, (error) => {
|
mountutils.unmountDisk(destination.device.device, (error) => {
|
||||||
debug('state:unmount', destination.device.device, error ? 'NOT OK' : 'OK')
|
debug('state:unmount', destination.device.device, error ? 'NOT OK' : 'OK')
|
||||||
|
destination.error = error
|
||||||
callback.call(this, error)
|
callback.call(this, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -241,6 +242,7 @@ class ImageWriter extends EventEmitter {
|
|||||||
|
|
||||||
diskpart.clean(destination.device.device).asCallback((error) => {
|
diskpart.clean(destination.device.device).asCallback((error) => {
|
||||||
debug('state:clean', destination.device.device, error ? 'NOT OK' : 'OK')
|
debug('state:clean', destination.device.device, error ? 'NOT OK' : 'OK')
|
||||||
|
destination.error = error
|
||||||
callback.call(this, error)
|
callback.call(this, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -286,12 +288,13 @@ class ImageWriter extends EventEmitter {
|
|||||||
fs.open(destination.device.raw, flags, (error, fd) => {
|
fs.open(destination.device.raw, flags, (error, fd) => {
|
||||||
debug('state:destination-open', destination.device.raw, error ? 'NOT OK' : 'OK')
|
debug('state:destination-open', destination.device.raw, error ? 'NOT OK' : 'OK')
|
||||||
destination.fd = fd
|
destination.fd = fd
|
||||||
|
destination.error = error
|
||||||
callback.call(this, error)
|
callback.call(this, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Check a destinstation against the drive constraints
|
* @summary Check a destination against the drive constraints
|
||||||
* @param {Object} destination - destination object
|
* @param {Object} destination - destination object
|
||||||
* @param {Function} callback - callback(error)
|
* @param {Function} callback - callback(error)
|
||||||
* @example
|
* @example
|
||||||
@ -300,16 +303,14 @@ class ImageWriter extends EventEmitter {
|
|||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
checkDriveConstraints (destination, callback) {
|
checkDriveConstraints (destination, callback) {
|
||||||
let error = null
|
|
||||||
|
|
||||||
if (!constraints.isDriveLargeEnough(destination.device, this.source)) {
|
if (!constraints.isDriveLargeEnough(destination.device, this.source)) {
|
||||||
error = errors.createUserError({
|
destination.error = errors.createUserError({
|
||||||
title: 'The image you selected is too big for this drive',
|
title: 'The image you selected is too big for this drive',
|
||||||
description: 'Please connect a bigger drive and try again'
|
description: 'Please connect a bigger drive and try again'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.call(this, error)
|
callback.call(this, destination.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -356,9 +357,11 @@ class ImageWriter extends EventEmitter {
|
|||||||
(done) => { this.unmountDevice(destination, done) },
|
(done) => { this.unmountDevice(destination, done) },
|
||||||
(done) => { this.removePartitionTable(destination, done) },
|
(done) => { this.removePartitionTable(destination, done) },
|
||||||
(done) => { this.openDestination(destination, done) }
|
(done) => { this.openDestination(destination, done) }
|
||||||
], (preparationError) => {
|
], () => {
|
||||||
destination.error = preparationError
|
if (destination.error) {
|
||||||
next(preparationError)
|
this.emit('fail', { device: destination.device.device, error: destination.error })
|
||||||
|
}
|
||||||
|
next(destination.error, destination)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -367,7 +370,11 @@ class ImageWriter extends EventEmitter {
|
|||||||
runParallel(tasks, (resultErrors, results) => {
|
runParallel(tasks, (resultErrors, results) => {
|
||||||
// We can start (theoretically) flashing now...
|
// We can start (theoretically) flashing now...
|
||||||
debug('write:prep:done', resultErrors)
|
debug('write:prep:done', resultErrors)
|
||||||
this._write()
|
if (_.every(resultErrors, _.identity)) {
|
||||||
|
this.emit('error', resultErrors[0])
|
||||||
|
} else {
|
||||||
|
this._write()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -433,23 +440,28 @@ class ImageWriter extends EventEmitter {
|
|||||||
this.emit('error', error)
|
this.emit('error', error)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pipeline.on('finish', (destination) => {
|
this.pipeline.on('complete', (destination) => {
|
||||||
this.bytesRead = this.source.bytesRead
|
this.bytesRead = this.source.bytesRead
|
||||||
|
|
||||||
let finishedCount = 0
|
let finishedCount = 0
|
||||||
|
let errorCount = 0
|
||||||
|
|
||||||
this.destinations.forEach((dest) => {
|
this.destinations.forEach((dest) => {
|
||||||
finishedCount += dest.finished ? 1 : 0
|
finishedCount += dest.finished ? 1 : 0
|
||||||
|
errorCount += dest.error ? 1 : 0
|
||||||
})
|
})
|
||||||
|
|
||||||
debug('write:finish', finishedCount, '/', this.destinations.size)
|
debug('write:finish', finishedCount, '/', this.destinations.size)
|
||||||
|
|
||||||
if (destination) {
|
if (_.has(destination, [ 'stream' ])) {
|
||||||
this.bytesWritten += destination.stream.bytesWritten
|
this.bytesWritten += destination.stream.bytesWritten
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finishedCount === this.destinations.size) {
|
if (finishedCount === this.destinations.size) {
|
||||||
if (this.verifyChecksums) {
|
if (errorCount === this.destinations.size) {
|
||||||
|
this.emit('error', destination.error)
|
||||||
|
this._finish()
|
||||||
|
} else if (this.verifyChecksums) {
|
||||||
debug('write:verify')
|
debug('write:verify')
|
||||||
this.verify()
|
this.verify()
|
||||||
} else {
|
} else {
|
||||||
@ -469,8 +481,20 @@ class ImageWriter extends EventEmitter {
|
|||||||
* imageWriter.verify()
|
* imageWriter.verify()
|
||||||
*/
|
*/
|
||||||
verify () {
|
verify () {
|
||||||
|
let bytesWritten = 0
|
||||||
|
|
||||||
|
// NOTE: We can't re-use `this.bytesWritten` here, as that will
|
||||||
|
// included bytes of streams that may have errored part way through
|
||||||
|
this.destinations.forEach((destination) => {
|
||||||
|
// Don't count errored destinations
|
||||||
|
if (destination.error || !destination.stream) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytesWritten += destination.stream.bytesWritten
|
||||||
|
})
|
||||||
|
|
||||||
const progressStream = new ProgressStream({
|
const progressStream = new ProgressStream({
|
||||||
length: this.bytesWritten,
|
length: bytesWritten,
|
||||||
time: 500
|
time: 500
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -500,7 +524,7 @@ class ImageWriter extends EventEmitter {
|
|||||||
const error = new Error(`Verification failed: ${JSON.stringify(this.checksum)} != ${JSON.stringify(checksum)}`)
|
const error = new Error(`Verification failed: ${JSON.stringify(this.checksum)} != ${JSON.stringify(checksum)}`)
|
||||||
error.code = 'EVALIDATION'
|
error.code = 'EVALIDATION'
|
||||||
destination.error = error
|
destination.error = error
|
||||||
this.emit('error', error)
|
this.emit('fail', { device: destination.device.device, error })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -681,6 +705,7 @@ class ImageWriter extends EventEmitter {
|
|||||||
this.destinations.forEach((destination) => {
|
this.destinations.forEach((destination) => {
|
||||||
if (destination.error) {
|
if (destination.error) {
|
||||||
debug('pipeline:skip', destination.device.device)
|
debug('pipeline:skip', destination.device.device)
|
||||||
|
destination.finished = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,21 +714,22 @@ class ImageWriter extends EventEmitter {
|
|||||||
autoClose: false
|
autoClose: false
|
||||||
})
|
})
|
||||||
|
|
||||||
destination.stream.once('finish', () => {
|
destination.stream.on('finish', () => {
|
||||||
debug('finish:unpipe', destination.device.device)
|
debug('finish:unpipe', destination.device.device)
|
||||||
destination.finished = true
|
destination.finished = true
|
||||||
pipeline.emit('finish', destination)
|
pipeline.emit('complete', destination)
|
||||||
pipeline.unpipe(destination.stream)
|
pipeline.unpipe(destination.stream)
|
||||||
})
|
})
|
||||||
|
|
||||||
destination.stream.once('error', (error) => {
|
destination.stream.on('error', (error) => {
|
||||||
debug('error:unpipe', destination.device.device)
|
debug('error:unpipe', destination.device.device)
|
||||||
destination.error = error
|
destination.error = error
|
||||||
destination.finished = true
|
destination.finished = true
|
||||||
pipeline.unpipe(destination.stream)
|
pipeline.unpipe(destination.stream)
|
||||||
|
this.emit('fail', { device: destination.device.device, error })
|
||||||
|
pipeline.emit('complete', destination)
|
||||||
})
|
})
|
||||||
|
|
||||||
pipeline.bind(destination.stream, 'error')
|
|
||||||
pipeline.pipe(destination.stream)
|
pipeline.pipe(destination.stream)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user