Skip to content

Commit

Permalink
lifecycle, transform error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ruy-dan committed Sep 19, 2024
1 parent b012537 commit 710dbd1
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 50 deletions.
5 changes: 5 additions & 0 deletions errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PearError extends Error {
static ERR_DIR_NONEMPTY = ERR_DIR_NONEMPTY
static ERR_OPERATION_FAILED = ERR_OPERATION_FAILED
static ERR_TRACER_FAILED = ERR_TRACER_FAILED
static ERR_TRANSFORM_FAILED = ERR_TRANSFORM_FAILED
static ERR_HTTP_GONE = ERR_HTTP_GONE
static ERR_HTTP_BAD_REQUEST = ERR_HTTP_BAD_REQUEST
static ERR_HTTP_NOT_FOUND = ERR_HTTP_NOT_FOUND
Expand Down Expand Up @@ -131,6 +132,10 @@ function ERR_TRACER_FAILED (msg) {
return new PearError(msg, 'ERR_TRACER_FAILED', ERR_TRACER_FAILED)
}

function ERR_TRANSFORM_FAILED (msg) {
return new PearError(msg, 'ERR_TRANSFORM_FAILED', ERR_TRANSFORM_FAILED)
}

function ERR_ASSERTION (msg) {
return new PearError(msg, 'ERR_ASSERTION', ERR_ASSERTION)
}
Expand Down
3 changes: 0 additions & 3 deletions lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ class Worker {
if (exitCode !== 0) pipe.emit('crash', { exitCode })
this.#unref()
})
sp.stderr?.on('data', (data) => {
pipe.emit('error', { data })
})
const pipe = sp.stdio[3]
return pipe
}
Expand Down
1 change: 0 additions & 1 deletion subsystems/sidecar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ class Sidecar extends ReadyResource {
seen.add(app.state.id)
const { pid, cmdArgs, cwd, dir, runtime, appling, env, run, options } = app.state
metadata.push({ pid, cmdArgs, cwd, dir, runtime, appling, env, run, options })
if (app.transformer) app.transformer.close()
const tearingDown = app.teardown()
if (tearingDown === false) client.close()
}
Expand Down
29 changes: 9 additions & 20 deletions subsystems/sidecar/lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const ReadyResource = require('ready-resource')
const streamx = require('streamx')
const listen = require('listen-async')
const Mime = require('./mime')
const picomatch = require('picomatch')
const Transformer = require('./transformer')
const transform = require('../../../lib/transform')
const { ERR_HTTP_BAD_REQUEST, ERR_HTTP_GONE, ERR_HTTP_NOT_FOUND } = require('../../../errors')
Expand Down Expand Up @@ -52,6 +51,8 @@ module.exports = class Http extends ReadyResource {
err.status = err.status || 404
} else if (err.code === 'SESSION_CLOSED') {
err.status = err.status || 503
} else if (err.code === 'ERR_TRANSFORM_FAILED') {
err.status = err.status || 500
} else {
console.error('Unknown Server Error', err)
err.status = 500
Expand Down Expand Up @@ -131,6 +132,8 @@ module.exports = class Http extends ReadyResource {
res.end(out)
return
}

if (ct === 'text/jsx') res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
}

if (await bundle.has(link.filename) === false) {
Expand Down Expand Up @@ -172,25 +175,11 @@ module.exports = class Http extends ReadyResource {
const meta = await bundle.entry(link.filename)
if (meta === null) throw new ERR_HTTP_NOT_FOUND(`Not Found: "${link.filename}"`)

const transforms = []
const patterns = app.state?.transforms

for (const ptn in patterns) {
const isMatch = picomatch(ptn)
if (isMatch(link.filename)) {
transforms.push(...patterns[ptn])
if (ptn.endsWith('.jsx') || ptn.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
}
}

if (transforms.length > 0) {
if (!app.transformer || app.transformer?.isClosed) {
const workerLink = `${this.sidecar.bundle.link}/transform`
app.transformer = new Transformer(app)
app.transformer.run(workerLink)
}
const buffer = await bundle.get(link.filename)
return app.transformer.queue(transforms, buffer, (buffer) => res.end(buffer))
const transformer = new Transformer(app, `${this.sidecar.bundle.link}/transform`)
const transformed = await transformer.transform(await bundle.get(link.filename), link.filename)
if (transformed !== null) {
res.end(transformed)
return
}

const stream = bundle.streamFrom(meta)
Expand Down
47 changes: 35 additions & 12 deletions subsystems/sidecar/lib/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ const Bundle = require('bare-bundle')
const FramedStream = require('framed-stream')
const b4a = require('b4a')
const path = require('bare-path')
const picomatch = require('picomatch')
const Worker = require('../../../lib/worker')
const { ERR_TRANSFORM_FAILED } = require('../../../errors')

module.exports = class Transformer {
app = null
Expand All @@ -11,23 +13,28 @@ module.exports = class Transformer {
stream = null
#queue = null

constructor (app) {
constructor (app, link, args) {
if (app.transformer) return app.transformer

this.app = app
this.worker = new Worker()
this.#queue = Promise.resolve()
this.link = link
this.args = args
app.transformer = this
}

run (link, args) {
this.pipe = this.worker.run(link, args, { stdio: ['ignore', 'pipe', 'pipe'] })
this.pipe.on('error', (err) => { console.error(err.data.toString()) })

open () {
this.#queue = Promise.resolve()
this.worker = new Worker()
this.pipe = this.worker.run(this.link, this.args, { stdio: 'inherit' })
this.stream = new FramedStream(this.pipe)
this.stream.on('end', () => this.stream.end())
}

close () {
this.#queue = null
this.stream.end()
this.worker = null
this.pipe.end()
this.stream.end()
}

queue (transforms, buffer, next) {
Expand All @@ -39,9 +46,20 @@ module.exports = class Transformer {
return this.#queue === null
}

async transform (transforms, buffer) {
if (transforms.length === 0) return buffer
async transform (buffer, filename) {
const transforms = []
const patterns = this.app.state?.transforms
for (const ptn in patterns) {
const isMatch = picomatch(ptn)
if (isMatch(filename)) {
transforms.push(...patterns[ptn])
}
}
if (transforms.length === 0) return null

if (this.isClosed) this.open()

const pipe = this.pipe
const stream = this.stream
stream.write(b4a.from(JSON.stringify(transforms)))

Expand All @@ -62,8 +80,13 @@ module.exports = class Transformer {
resolve(data)
})

stream.on('error', (data) => {
reject(new Error('Transform error:', data.toString()))
stream.on('error', () => {
reject(new ERR_TRANSFORM_FAILED(`Transform failed: ${filename}`))

})

pipe.on('crash', () => {
reject(new ERR_TRANSFORM_FAILED(`Transform failed: ${filename}`))
})
})

Expand Down
33 changes: 19 additions & 14 deletions transform/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/* global Pear */
const Module = require('bare-module')
const FramedStream = require('framed-stream')
const { isWindows } = require('which-runtime')

const pipe = Pear.worker.pipe()
const stream = new FramedStream(pipe)
Expand All @@ -14,7 +13,13 @@ let buffer = null
let timer = null

stream.on('data', (data) => {
timeout(10_000)
clearTimeout(timer)
timer = setTimeout(() => {
if (transforms.length === 0) {
stream.end()
pipe.end()
}
}, 10_000)

if (transforms.length === 0) {
transforms = JSON.parse(data.toString())
Expand All @@ -27,10 +32,10 @@ stream.on('data', (data) => {
buffer = bundles.reduce((source, bundle, index) => {
const config = transforms[index]
const { options = {} } = typeof config === 'string' ? {} : config
const root = isWindows ? 'file:///c:' : 'file://'
const transform = Module.load(new URL(root + '/transform.bundle'), bundle).exports

return transform(source, options)
try {
const transform = Module.load(new URL('app:/transform.bundle'), bundle).exports
return transform(source, options)
} catch (err){ return stream.emit('error', err) }
}, buffer)

stream.write(buffer)
Expand All @@ -43,15 +48,15 @@ stream.on('data', (data) => {

bundles.push(data)
})
stream.on('end', () => {

stream.on('error', () => {
clearTimeout(timer)
stream.destroy()
pipe.end()
Pear.exit()
Pear.exit(1)
})
stream.on('error', (err) => console.error(err))

function timeout (ms) {
stream.on('end', () => {
clearTimeout(timer)
timer = setTimeout(async () => {
if (transforms.length === 0) stream.end()
}, ms)
}
pipe.end()
})

0 comments on commit 710dbd1

Please sign in to comment.