[feature] make client-side nonce calculation multi-threaded (#4219)

# Description

Thank you in part to f0x for nerd-sniping me into banging this together :p

## Checklist

- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [ ] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4219
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2025-05-31 13:45:32 +02:00 committed by tobi
commit a82d574acc
2 changed files with 81 additions and 45 deletions

View file

@ -45,51 +45,85 @@ document.addEventListener("DOMContentLoaded", function() {
// data attributes on the nollamas section. // data attributes on the nollamas section.
const seed = nollamas.dataset.nollamasSeed; const seed = nollamas.dataset.nollamasSeed;
const challenge = nollamas.dataset.nollamasChallenge; const challenge = nollamas.dataset.nollamasChallenge;
const threads = navigator.hardwareConcurrency;
if (typeof(threads) != "number" || threads < 1) { threads = 1; }
console.log("seed:", seed); // eslint-disable-line no-console console.log("seed:", seed); // eslint-disable-line no-console
console.log("challenge:", challenge); // eslint-disable-line no-console console.log("challenge:", challenge); // eslint-disable-line no-console
console.log("threads:", threads); // eslint-disable-line no-console
// Prepare the worker with task function. // Create an array to track
const worker = new Worker("/assets/dist/nollamasworker.js"); // all workers such that we
// can terminate them all.
const workers = [];
const terminateAll = () => { workers.forEach((worker) => worker.terminate() ); };
// Get time before task completion.
const startTime = performance.now(); const startTime = performance.now();
worker.postMessage({
challenge: challenge,
seed: seed,
});
// Set the main worker function. // Prepare and start each worker,
worker.onmessage = function (e) { // adding them to the workers array.
if (e.data.done) { for (let i = 0; i < threads; i++) {
const endTime = performance.now(); const worker = new Worker("/assets/dist/nollamasworker.js");
const duration = endTime - startTime; workers.push(worker);
// Remove spinner and replace it with a tick // On any error terminate.
// and info about how long it took to solve. worker.onerror = (ev) => {
nollamas.removeChild(spinner); console.error("worker error:", ev); // eslint-disable-line no-console
const solutionWrapper = document.createElement("div"); terminateAll();
solutionWrapper.className = "nollamas-status"; };
const tick = document.createElement("i"); // Post worker message, where each
tick.className = "fa fa-check fa-2x fa-fw"; // worker will compute nonces in range:
tick.setAttribute("title","Solved!"); // $threadNumber + $totalThreads * n
tick.setAttribute("aria-label", "Solved!"); worker.postMessage({
solutionWrapper.appendChild(tick); challenge: challenge,
threads: threads,
thread: i,
seed: seed,
});
const took = document.createElement("span"); // Set main on-success function.
const solvedText = `Solved after ${e.data.nonce} iterations, in ${duration.toString()}ms!`; worker.onmessage = function (e) {
took.appendChild(document.createTextNode(solvedText)); if (e.data.done) {
solutionWrapper.appendChild(took); // Stop workers.
terminateAll();
nollamas.appendChild(solutionWrapper); // Log which thread found the solution.
console.log("solution from:", e.data.thread); // eslint-disable-line no-console
// Wait for 500ms before redirecting, to give // Get total computation duration.
// visitors a shot at reading the message, but const endTime = performance.now();
// not so long that they have to wait unduly. const duration = endTime - startTime;
setTimeout(() => {
let url = new URL(window.location.href); // Remove spinner and replace it with a tick
url.searchParams.set("nollamas_solution", e.data.nonce); // and info about how long it took to solve.
window.location.replace(url.toString()); nollamas.removeChild(spinner);
}, 500); const solutionWrapper = document.createElement("div");
} solutionWrapper.className = "nollamas-status";
};
const tick = document.createElement("i");
tick.className = "fa fa-check fa-2x fa-fw";
tick.setAttribute("title","Solved!");
tick.setAttribute("aria-label", "Solved!");
solutionWrapper.appendChild(tick);
const took = document.createElement("span");
const solvedText = `Solved after ${e.data.nonce} iterations by worker ${e.data.thread} of ${threads}, in ${duration.toString()}ms!`;
took.appendChild(document.createTextNode(solvedText));
solutionWrapper.appendChild(took);
nollamas.appendChild(solutionWrapper);
// Wait for 500ms before redirecting, to give
// visitors a shot at reading the message, but
// not so long that they have to wait unduly.
setTimeout(() => {
let url = new URL(window.location.href);
url.searchParams.set("nollamas_solution", e.data.nonce);
window.location.replace(url.toString());
}, 500);
}
};
}
}); });

View file

@ -19,10 +19,10 @@
import sha256 from "./sha256"; import sha256 from "./sha256";
let compute = async function(seedStr, challengeStr) { let compute = async function(seedStr, challengeStr, start, iter) {
const textEncoder = new TextEncoder(); const textEncoder = new TextEncoder();
let nonce = 0; let nonce = start;
while (true) { // eslint-disable-line no-constant-condition while (true) { // eslint-disable-line no-constant-condition
// Create possible solution string from challenge string + nonce. // Create possible solution string from challenge string + nonce.
@ -38,17 +38,19 @@ let compute = async function(seedStr, challengeStr) {
return nonce; return nonce;
} }
// Iter. // Iter nonce.
nonce++; nonce += iter;
} }
}; };
onmessage = async function(e) { onmessage = async function(e) {
console.log('worker started'); // eslint-disable-line no-console const thread = e.data.thread;
const threads = e.data.threads;
console.log("worker started:", thread); // eslint-disable-line no-console
// Compute nonce value that produces 'challenge' for seed. // Compute nonce value that produces 'challenge', for our allotted thread.
let nonce = await compute(e.data.seed, e.data.challenge); let nonce = await compute(e.data.seed, e.data.challenge, thread, threads);
// Post the solution nonce back to caller. // Post the solution nonce back to caller with thread no.
postMessage({ nonce: nonce, done: true }); postMessage({ nonce: nonce, done: true, thread: thread });
}; };