Skip to content

Conversation

@dpvc
Copy link
Member

@dpvc dpvc commented Mar 5, 2025

Overview

The main purposes of this PR are to:

  1. Add the ability to cancel worker tasks (and to do so when MathItems are removed from the document math list)
  2. Support using the components in node applications. (The current code only works for direct imports of the MathJax modules. This extends support to using the MathJax components as well.)

It also changes how the worker handles promises. Rather than having the GeneratorPool create promises, the WorkerHandler's Post() command now does that automatically, so every command send to the worker pool has a promise that resolves when that command is complete.

This change also includes the ability to pass data back from the worker to the client via that promise. This allows the worker to send the speech structure data back via the promise rather than a client Attach() command. The client now uses that data to do the attach action itself. This allows the worker to start processing other commands while the client is attaching the speech from the previous one, since the worker will have finished its command when it passes back the speech structure, rather than having to wait fo the client to attach and then finish.

The Details

In the worker pool configuration file, we add the verisons check (so we don't get an error message about missing version information when a node application's LiteIFrame loads the worker pool data via asyncLoad()).

We add the speech component to the a11y/util.jsso that the combined components that include the assisitive tools will include the speech component.

We extend the asyncLoad() definition in the core component to potentially handle loading node packages (we do that in the LiteIFrame by loading the node:module node package). In a node application that has set the require configuration option properly to either the node require or to (file) => import(file), or an equivalent, this will allow loading of core node modules.

The version of SRE in the lab directory now sets up the mathmaps path to work in the lab. (The typo that I had in the SREfeature that you corrected actually allowed it to work before, but now that that has been corrected, it no longer does, but this takes care of that.)

The speech and Braille controls are removed from the semantic-enrich component, since they are not used there, and are already in the speech component, where they are used.

In speech.ts, the GeneratorPool's Speech() function now returns a promise (see below), so we take advantage of that insteach of using a try/catch construction. We also push the promise into the document's renderPromises array so that MathJax.typesetPromise() will wait for it before resolving. That way, the speech will be on the DOM nodes when the typeset promise resolves (as it is in beta.7).

The MathItem's clear() method now cancels its speech task, if there is one.

The MathDocument now gets a done() method that terminates the worker pool.

In GeneratorPool.ts:

  • Since the Post() command of the WorkerHandler now creates promises automatically (see below), we don't need to get our own promises.
  • The promises can return values, so they are Promise<any> not Promise<void>.
  • The Speech() command now saves that promise and returns it (as do nextRules and nextStyle).
  • A new cancel() method cancels any task saved for this MathItem.

In MessageTypes.ts, the PromiseFunctions are no longer needed, and we add some types for the speech structure that will be passed back from the worker.

In WebWorker.ts:

  • The resolve() function can now pass an argument.
  • The Post() method now creates a promise for the task automatically.
  • The Speech() method is moved down to be next to the nextRules and nextStyle methods.
  • A new Cancel() method is added to allow a pending task to be canceled (removed from the task list) and its promise rejected. We might want to make a convention for this so that warnings aren't produced when tasks are canceled explicitly.
  • The methods that post messages to the worker now all return promises.
  • The Speech() command is now an async function that waits for the worker to return the speech structure and then calls Attach() on using that. (The worker no longer calls Attach() itself, but just returns the data, so it can go on to do other tasks while the speech is being attached.)
  • Similarly for nextRules and nextStyle.
  • The Attach() function has been moved out the worker commands and is now just a regular method. It is also broken into three parts (rather than creating functions internally), and setSpecialAttributes() is moved here along with the other separated out setSpeechAttribute() and setSpeechAttributes().
  • The Terminate() method now rejects all the tasks in the list, and before terminating the worker pool.
  • The Stop() method now waits for the termination to complete before removing the iframe.
  • The Finished() method now returns either the result from the worker, or an error message from it.

In speech-worker.ts:

  • We define the structure types (just to be more expressive), and use them in the copyStructure() commands.
  • The event listener is restructured slightly. All the commands now return promises (se below), so we take advantage of that to then() and catch() rather than a try/catch structure. An error state is indicated by an error property of the msg passed to Finished() (see below).
  • We use WorkerFunction and WorkerResult types for more specificity.
  • The feature() command is removed, since SRE is already configured and loaded, so this would have no effect.
  • The setup() command now waits for the setup to take effect (though the command is not used currently).
  • The Finished() now takes a msg parameter that is sent back to the client. The success property is set according to whether there is an error message or not.
  • The Speech() action is simplified a bit, and now returns the speech structure data rather than asking the client to attach. The client gets the structure data from the promise it got when posting the worker command, and does the attach operation itself.

The LiteIFrame element indicates that asyncLoad needs to load a node module so that the asyncLoad from the core component will not use the Package module's loader.

The loader.ts file now allows the Load() function to return the results of the load operations (i.e., the exported values of the packages that were loaded). This allows the LiteIFrame to get the exported values from the speech-workerpool when components are being used in node. The results are stored in new results properties of the Package data (see below).

In package.ts, we now retain the result of loading a package, i.e., its exported values, so that the component loader can return those.

In startup.ts, we fix a typo with the document used for the toMML() function (argh), and create a new MathJax.done() function that is used to terminate the worker pool, if it was started.

In MathDocument.ts:

  • We add the done() method that can be used to clean up anything when the document is no longer needed. (We use it to terminate the worker pool in the speech handler.)
  • The clearMathItemsWithin() now calls each MathItem's clear() method (used to cancel its speech task, if any).

In MathItem.ts, we add the new clear() method, and call it from the removeFromDocument() method, so that if a MathItem is removed, its tasks are canceled.

Finally, in HTMLMathItem.ts, we make sure the super-class removeFromDocument() is called (so the clear() is performed).

@dpvc dpvc requested a review from zorkow March 5, 2025 14:49
@dpvc dpvc added this to the v4.0 milestone Mar 5, 2025
@dpvc
Copy link
Member Author

dpvc commented Mar 5, 2025

To test in the lab, be sure to run

pnpm -s lab:sre

first, and add

loader: {
  paths: {
    sre: './node_modules/mathjax-full/mjs/a11y/sre'
  }
}

to the configuration in lib/v4-lab.js.

@dpvc
Copy link
Member Author

dpvc commented Apr 15, 2025

I forgot that #1231 was targeted to this PR rather than develop. Since I just merged that, those changes are now in this branch, and you will see them listed as changes here. I can revert #1231 if you want, in order to make this PR cleaner, and re-merge it later. Sorry about that.

Copy link
Member

@zorkow zorkow left a comment

Choose a reason for hiding this comment

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

A few minor things. And one point for discussion to improve the performance of cancel. But it is not a blocking concern.

if (i > 0) {
this.tasks[i].reject(`Task ${this.tasks[i].cmd.cmd} cancelled`);
this.tasks.splice(i, 1);
}
Copy link
Member

Choose a reason for hiding this comment

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

This looks like it might be better suited to implement as a map to get $O(1)$ access. Of course, this would mean that we would have to replace the push and shift operations. Since Maps preserve order of entry insertion, this should not be a problem using entries().next().

For the time being, I think it is probably good enough as I assume cancellation will be a rare event. But in case this should become a performance problem one day we could refactor this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wasn't particularly happy about that, either, but didn't want to make bigger changes just for this. As you say, it is likely not going to come into play very often, and can be dealt with later.

Since Maps preserve order of entry insertion

Is that guaranteed, or is it just the way things currently are? It sound like that could be a nice solution.

Copy link
Member

Choose a reason for hiding this comment

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

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map claims

The Map object holds key-value pairs and remembers the original insertion order of the keys. 

According to the official spec insertion order is definitely required for foreach:
https://262.ecma-international.org/#sec-map.prototype.foreach

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the details. Looks like it should be a straight-forward change, then.

@dpvc dpvc merged commit 87a08b0 into develop Apr 17, 2025
@dpvc dpvc deleted the update/node-workers branch April 17, 2025 10:25
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.

3 participants