-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Two workers & worker termination #198
Conversation
I changed the shape of the encoders/preprocessors. |
import wasmUrl from '../../../codecs/imagequant/imagequant.wasm'; | ||
import { QuantizeOptions } from './processor-meta'; | ||
|
||
let emscriptenModule: Promise<QuantizerModule>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed how we do these types of files a bit. Instead of having a class, I just treat it as a module. The emscripten module is initialised on first use.
@@ -0,0 +1,41 @@ | |||
import { expose } from 'comlink'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the worker. It exposes a method for everything we workerise, but it loads the bulk of the code lazily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this a lot better than the per-encoder approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great for now. Just for documentation purposes: You said in person we’d probably wanna explore some form of code generation or something to make adding new codecs a bit easier in the future.
@@ -0,0 +1,194 @@ | |||
import { proxy } from 'comlink'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main-thread part of processor-worker. It exposes all the image processing methods, and cancels any pending jobs.
Not to go all “It works on my machine” on you, but it works on my machine? I.e. your modified |
Huh, does the 2nd image not look all fucked? I guess I'll show you next
week.
…On Sat, 13 Oct 2018, 17:58 Surma ***@***.*** wrote:
Not to go all “It works on my machine” on you, but it works on my machine?
I.e. your modified example.html runs without any errors for me.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#198 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAFtmvUOcXsEX0A2wdJMGSAKz53g057gks5ukhupgaJpZM4XasTL>
.
|
aab1fb8
to
eda1658
Compare
a4c173e
to
95a1251
Compare
@@ -1,4 +1,4 @@ | |||
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder'; | |||
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder-meta'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good call splitting out the metadata, that helps resolve some oddities with worker/noworker module loading I was lamenting.
src/codecs/imagequant/processor.ts
Outdated
// prop solves this for now. | ||
// See: https://github.com/kripken/emscripten/blob/incoming/src/postamble.js#L129 | ||
// TODO(surma@): File a bug with Emscripten on this. | ||
delete (m as any).then; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this isn't part of the PR, but could we change this so it doesn't require the delete? Looking at the source, the thennable never fails, so we could just do:
m.then(resolve)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I bet we could extract the initialization into a helper method:
// initWasmModule(imagequant, wasmUrl)
function initWasmModule(fn, wasmUrl) {
return new Promise((resolve) => {
const m = fn({
// Just to be safe, don’t automatically invoke any wasm functions
noInitialRun: false,
locateFile(url: string): string {
// Redirect the request for the wasm binary to whatever webpack gave us.
if (url.endsWith('.wasm')) return wasmUrl;
return url;
},
onRuntimeInitialized() {
m.then(resolve);
}
});
});
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m.then(resolve)
Yeah, that seems less weird.
Also I bet we could extract the initialization into a helper method
Agreed. Good point.
src/codecs/imagequant/processor.ts
Outdated
return new Promise((resolve) => { | ||
const m = imagequant({ | ||
// Just to be safe, don’t automatically invoke any wasm functions | ||
noInitialRun: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed, this seems to do the opposite of what the comment above indicates.
no initial run = false
--> initial run = true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@surma ^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, this should be true
. Not sure how false
got in here.
@@ -0,0 +1,41 @@ | |||
import { expose } from 'comlink'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this a lot better than the per-encoder approach.
src/codecs/processor.ts
Outdated
if (needsWorker) self.clearTimeout(this._workerTimeout); | ||
|
||
if (!this._worker && needsWorker) { | ||
// Webpack's worker loader does magic here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: worker-plugin
Unfortunately that doesn't work. The module resolves with itself, and |
Yeah, see the comment and the linked GitHub issue as to why I did the weird |
Seems like I never added the GitHub issue: emscripten-core/emscripten#6563 |
codecs/mozjpeg_enc/example.html
Outdated
const img = document.createElement('img'); | ||
img.src = blobURL; | ||
document.body.appendChild(img); | ||
for (let i = 0; i < 10; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we undo this change now that mozjpeg is fixed?
@@ -1,47 +1,21 @@ | |||
import * as wasmWebp from './webp/decoder'; | |||
import * as browserWebp from './webp/decoder'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you inline these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like a drop in complexity while we only have one decoder. This file is smaller & IMO easier to follow as a result.
@@ -0,0 +1,41 @@ | |||
import { expose } from 'comlink'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great for now. Just for documentation purposes: You said in person we’d probably wanna explore some form of code generation or something to make adding new codecs a bit easier in the future.
* processing jobs require a worker (e.g. the main thread canvas encodes), use the needsWorker | ||
* option to control this. | ||
*/ | ||
private static _processingJob(options: ProcessingJobOptions = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NICE
src/codecs/processor.ts
Outdated
this._worker.terminate(); | ||
this._worker = undefined; | ||
}, | ||
10000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make this a public instance prop?
src/codecs/processor.ts
Outdated
private _abortRejector?: (err: Error) => void; | ||
private _busy = false; | ||
private _latestJobId: number = 0; | ||
private _workerTimeout: number = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can haz some comments on these fields? E.g. it wasn’t clear for the timeout is for. To kill a hanging worker or an unused one?
src/codecs/processor.ts
Outdated
// Wait for the operation to settle. | ||
await returnVal.catch(() => {}); | ||
|
||
if (jobId === this._latestJobId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather make this an early return when the jobId
doesn’t match?
src/codecs/processor.ts
Outdated
if (jobId === this._latestJobId) { | ||
this._busy = false; | ||
|
||
if (needsWorker) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super minor nit: I’d find this if
more expressive if you did if (this._worker)
.
“If there is a worker, start the timeout countdown.”
onRuntimeInitialized() { | ||
// An Emscripten is a then-able that resolves with itself, causing an infite loop when you | ||
// wrap it in a real promise. Delete the `then` prop solves this for now. | ||
// https://github.com/kripken/emscripten/issues/5820 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
7864739
to
6e1e4aa
Compare
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
@surma feedback should be addressed now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Refactoring codecs * Plugging in new processor * Fixing decorator * MozJPEG free issue * Better worker aborting, and terminate workers that aren't used for 10 seconds * Better comment * Ooops, half-typed comment * Uncommenting problematic line * Surma fixed it! * Abstracting WASM initialisation * Better comment * Don't need this. * Adding ticket * noInitalRun * Reverting MozJPEG issue demo * Making a const for worker timeout * Inline docs * Bail early rather than nesting * Addressing nits
Building on the ideas in #192, but ensuring we only have two workers on the go.
This involved a lot of refactoring, so I'm really sorry that this PR ballooned. Free free to throw it back at me (if you've got a better way of doing it).
I'll leave some comments to try and make things easier.
@surma it doesn't look like free() is working correctly for mozjpeg. Could you take a look? I updated
example.html
to show the issue. I was getting "out of bounds" errors, but now it just seems to screw up the second image.