Skip to content

Commit 34fd3ce

Browse files
authored
feat(worker): accept platform options under android, deprecate androidPriority (#2041)
* feat: accept platform options under android, deprecate androidPriority Worker options now carry Android-specific settings in an `android` namespace object, so future platform options have a place to live instead of accumulating as `androidSomething` keys on the top level: new Worker("./w.js", { android: { priority: "lowest" } }) `android.priority` is validated strictly — a non-object `android`, or a priority that is neither one of the camelCase THREAD_PRIORITY_* names nor a nice value, throws a TypeError — while unknown keys inside `android` are ignored so later options can be added without breaking older runtimes. `androidPriority` keeps its current behavior and logs a one-time per-process deprecation warning; `android.priority` takes precedence when both are given. An option getter that throws now stops construction rather than being swallowed into the default priority, and an option error reaches JS as a real TypeError instance so `e instanceof TypeError` holds. * fix: clamp numeric worker priorities before converting to int
1 parent 60d03e9 commit 34fd3ce

6 files changed

Lines changed: 343 additions & 50 deletions

File tree

test-app/app/src/main/assets/app/mainpage.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ require("./tests/testWebAssembly");
2727
require("./tests/testEventLoop");
2828
require("./tests/testMultithreadedJavascript");
2929
require("./tests/testWorkerTerminateDuringLoad");
30+
require("./tests/testWorkerOptions");
3031
require("./tests/testInterfaceDefaultMethods");
3132
require("./tests/testInterfaceStaticMethods");
3233
require("./tests/testMetadata");
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
describe("Worker platform options", function () {
2+
var entry = "./workerOptionsPriorityWorker.js";
3+
4+
// Jasmine arms a spec's async timeout before calling it, so the interval
5+
// has to be raised ahead of the spec, not inside it. A thread niced down
6+
// to 19 boots a whole isolate on whatever CPU is left over; on a loaded
7+
// host that has taken well over 10 s.
8+
var originalTimeout;
9+
beforeEach(function () {
10+
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
11+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
12+
});
13+
afterEach(function () {
14+
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
15+
});
16+
17+
var reportPriority = function (options, done, check) {
18+
var worker = options === undefined ? new Worker(entry) : new Worker(entry, options);
19+
var settled = false;
20+
var finish = function () {
21+
if (settled) {
22+
return;
23+
}
24+
settled = true;
25+
worker.terminate();
26+
done();
27+
};
28+
// A throw inside either handler must still settle the spec and
29+
// terminate the worker; Jasmine only guards the spec body itself.
30+
worker.onmessage = function (msg) {
31+
try {
32+
check(msg.data.priority);
33+
} finally {
34+
finish();
35+
}
36+
};
37+
worker.onerror = function (e) {
38+
try {
39+
expect(String(e && e.message ? e.message : e)).toBe("<no worker error>");
40+
} finally {
41+
finish();
42+
}
43+
};
44+
};
45+
46+
// Only the non-negative nice values are asserted exactly: lowering a
47+
// thread's nice value needs a privilege the app may not hold, so the
48+
// negative names are covered below by starting a worker instead.
49+
var priorities = [
50+
["lowest", 19],
51+
["background", 10],
52+
["lessFavorable", 1],
53+
["default", 0]
54+
];
55+
56+
priorities.forEach(function (pair) {
57+
it("runs the worker thread at " + pair[0] + " priority", function (done) {
58+
reportPriority({ android: { priority: pair[0] } }, done, function (priority) {
59+
expect(priority).toBe(pair[1]);
60+
});
61+
});
62+
});
63+
64+
it("accepts a negative priority name", function () {
65+
var worker;
66+
expect(function () {
67+
worker = new Worker(entry, { android: { priority: "urgentAudio" } });
68+
}).not.toThrow();
69+
worker.terminate();
70+
});
71+
72+
it("accepts a raw nice value", function (done) {
73+
reportPriority({ android: { priority: 12 } }, done, function (priority) {
74+
expect(priority).toBe(12);
75+
});
76+
});
77+
78+
it("clamps a nice value above the kernel range", function (done) {
79+
reportPriority({ android: { priority: 100 } }, done, function (priority) {
80+
expect(priority).toBe(19);
81+
});
82+
});
83+
84+
it("clamps a nice value past the range of a 32-bit integer", function (done) {
85+
reportPriority({ android: { priority: 4294967295 } }, done, function (priority) {
86+
expect(priority).toBe(19);
87+
});
88+
});
89+
90+
// Asserted by starting the worker rather than by the reported nice value,
91+
// for the same privilege reason as the negative names above.
92+
it("accepts a nice value past the negative end of a 32-bit integer", function () {
93+
var worker;
94+
expect(function () {
95+
worker = new Worker(entry, { android: { priority: -4294967296 } });
96+
}).not.toThrow();
97+
worker.terminate();
98+
});
99+
100+
it("still honors the deprecated androidPriority option", function (done) {
101+
reportPriority({ androidPriority: "lowest" }, done, function (priority) {
102+
expect(priority).toBe(19);
103+
});
104+
});
105+
106+
it("prefers android.priority over androidPriority when both are given", function (done) {
107+
reportPriority({ android: { priority: "default" }, androidPriority: "lowest" }, done,
108+
function (priority) {
109+
expect(priority).toBe(0);
110+
});
111+
});
112+
113+
it("ignores unknown keys inside android", function (done) {
114+
reportPriority({ android: { priority: "lowest", somethingElse: 42 } }, done,
115+
function (priority) {
116+
expect(priority).toBe(19);
117+
});
118+
});
119+
120+
it("starts a worker given no options at all", function (done) {
121+
reportPriority(undefined, done, function (priority) {
122+
expect(typeof priority).toBe("number");
123+
});
124+
});
125+
126+
it("treats android: null like an absent android", function (done) {
127+
reportPriority({ android: null, androidPriority: "lowest" }, done, function (priority) {
128+
expect(priority).toBe(19);
129+
});
130+
});
131+
132+
it("propagates the error thrown by an option getter", function () {
133+
var boom = new Error("boom");
134+
var options = new Proxy({}, {
135+
get: function (target, key) {
136+
if (key === "android") {
137+
throw boom;
138+
}
139+
return undefined;
140+
}
141+
});
142+
var thrown;
143+
try {
144+
new Worker(entry, options);
145+
} catch (e) {
146+
thrown = e;
147+
}
148+
expect(thrown).toBe(boom);
149+
});
150+
151+
it("throws a TypeError when android is not an object", function () {
152+
expect(function () {
153+
new Worker(entry, { android: 42 });
154+
}).toThrowError(TypeError, /"android"/);
155+
});
156+
157+
it("throws a TypeError for an unknown android.priority", function () {
158+
expect(function () {
159+
new Worker(entry, { android: { priority: "highest" } });
160+
}).toThrowError(TypeError, /"android\.priority"/);
161+
});
162+
163+
it("throws a TypeError for an android.priority that is neither a name nor a number",
164+
function () {
165+
expect(function () {
166+
new Worker(entry, { android: { priority: {} } });
167+
}).toThrowError(TypeError, /"android\.priority"/);
168+
});
169+
170+
it("throws a TypeError for a NaN android.priority", function () {
171+
expect(function () {
172+
new Worker(entry, { android: { priority: NaN } });
173+
}).toThrowError(TypeError, /"android\.priority"/);
174+
});
175+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Entry for testWorkerOptions: reports the nice value the runtime gave this
2+
// worker's thread, which is the only observable effect of the
3+
// `android.priority` option.
4+
postMessage({ priority: android.os.Process.getThreadPriority(android.os.Process.myTid()) });

0 commit comments

Comments
 (0)