Skip to content
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

Merged
merged 19 commits into from
Oct 28, 2018
Merged

Two workers & worker termination #198

merged 19 commits into from
Oct 28, 2018

Conversation

jakearchibald
Copy link
Collaborator

@jakearchibald jakearchibald commented Oct 13, 2018

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.

@jakearchibald jakearchibald changed the base branch from master to app-split October 13, 2018 15:57
@jakearchibald
Copy link
Collaborator Author

I changed the shape of the encoders/preprocessors. encoder-meta.ts contains stuff like default options, types, basically things we're happy being on the main thread. encoder.ts becomes the wasm stuff.

import wasmUrl from '../../../codecs/imagequant/imagequant.wasm';
import { QuantizeOptions } from './processor-meta';

let emscriptenModule: Promise<QuantizerModule>;
Copy link
Collaborator Author

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';
Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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';
Copy link
Collaborator Author

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.

@surma
Copy link
Collaborator

surma commented Oct 13, 2018

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.

@jakearchibald
Copy link
Collaborator Author

jakearchibald commented Oct 13, 2018 via email

@@ -1,4 +1,4 @@
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder';
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder-meta';
Copy link
Collaborator

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.

// 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;
Copy link
Collaborator

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)

Copy link
Collaborator

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);
      }
    });
  });
}

Copy link
Collaborator Author

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.

return new Promise((resolve) => {
const m = imagequant({
// Just to be safe, don’t automatically invoke any wasm functions
noInitialRun: false,
Copy link
Collaborator

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@surma ^

Copy link
Collaborator

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';
Copy link
Collaborator

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.

if (needsWorker) self.clearTimeout(this._workerTimeout);

if (!this._worker && needsWorker) {
// Webpack's worker loader does magic here.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: worker-plugin

@jakearchibald
Copy link
Collaborator Author

@developit

m.then(resolve)

Unfortunately that doesn't work. The module resolves with itself, and resolve() will attempt to unwrap the thennables, which it can't because Emscripten is doing extra weird stuff.

@surma
Copy link
Collaborator

surma commented Oct 15, 2018

Yeah, see the comment and the linked GitHub issue as to why I did the weird delete thing.

@surma
Copy link
Collaborator

surma commented Oct 15, 2018

Seems like I never added the GitHub issue: emscripten-core/emscripten#6563

const img = document.createElement('img');
img.src = blobURL;
document.body.appendChild(img);
for (let i = 0; i < 10; i++) {
Copy link
Collaborator

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';
Copy link
Collaborator

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?

Copy link
Collaborator Author

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';
Copy link
Collaborator

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 = {}) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE

this._worker.terminate();
this._worker = undefined;
},
10000,
Copy link
Collaborator

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?

private _abortRejector?: (err: Error) => void;
private _busy = false;
private _latestJobId: number = 0;
private _workerTimeout: number = 0;
Copy link
Collaborator

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?

// Wait for the operation to settle.
await returnVal.catch(() => {});

if (jobId === this._latestJobId) {
Copy link
Collaborator

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?

if (jobId === this._latestJobId) {
this._busy = false;

if (needsWorker) {
Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@googlebot
Copy link

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 cla/google commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the cla label to yes (if enabled on your project), and then merge this pull request when appropriate.

@jakearchibald jakearchibald changed the base branch from app-split to master October 20, 2018 12:25
@jakearchibald
Copy link
Collaborator Author

@surma feedback should be addressed now

Copy link
Collaborator

@surma surma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@jakearchibald jakearchibald merged commit 43def79 into master Oct 28, 2018
@jakearchibald jakearchibald mentioned this pull request Oct 28, 2018
@jakearchibald jakearchibald deleted the processor-worker branch October 30, 2018 13:19
alisaitbilgi pushed a commit to alisaitbilgi/squoosh that referenced this pull request Feb 19, 2019
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants