Skip to content

Commit

Permalink
worker configures VM using manifest's creation object - consistent an…
Browse files Browse the repository at this point in the history
…d more powerful #1195
  • Loading branch information
phoddie committed Aug 20, 2023
1 parent f6f26e6 commit 1614168
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 75 deletions.
6 changes: 3 additions & 3 deletions build/devices/esp/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Moddable Tech, Inc.
* Copyright (c) 2016-2023 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK Runtime.
*
Expand Down Expand Up @@ -81,7 +81,7 @@ void setup()
wifi_set_opmode_current(NULL_MODE);

#ifdef mxDebug
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);
if (!gThe) {
modLog("can't clone: no memory?");
while (true)
Expand All @@ -90,7 +90,7 @@ void setup()

modRunMachineSetup(gThe);
#else
modRunMachineSetup(modCloneMachine(0, 0, 0, 0, NULL));
modRunMachineSetup(modCloneMachine(NULL, NULL));
#endif
}

Expand Down
4 changes: 2 additions & 2 deletions build/devices/esp32/xsProj-esp32/main/main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Moddable Tech, Inc.
* Copyright (c) 2016-2023 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK Runtime.
*
Expand Down Expand Up @@ -123,7 +123,7 @@ void loop_task(void *pvParameter)
#endif

while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
2 changes: 1 addition & 1 deletion build/devices/esp32/xsProj-esp32c3/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ void loop_task(void *pvParameter)
#endif

while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
2 changes: 1 addition & 1 deletion build/devices/esp32/xsProj-esp32s2/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ void loop_task(void *pvParameter)
#endif

while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
2 changes: 1 addition & 1 deletion build/devices/esp32/xsProj-esp32s3/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ void loop_task(void *pvParameter)
#endif

while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
4 changes: 2 additions & 2 deletions build/devices/nrf52/base/xsmain.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Moddable Tech, Inc.
* Copyright (c) 2016-2023 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK Runtime.
*
Expand Down Expand Up @@ -68,7 +68,7 @@ void loop_task(void *pvParameter)

#if MODDEF_XS_TEST
while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
2 changes: 1 addition & 1 deletion build/devices/pico/base/xsmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void xs_setup(void)
#endif

while (true) {
gThe = modCloneMachine(0, 0, 0, 0, NULL);
gThe = modCloneMachine(NULL, NULL);

modRunMachineSetup(gThe);

Expand Down
36 changes: 26 additions & 10 deletions documentation/base/worker.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Worker
Copyright 2018-2023 Moddable Tech, Inc.<BR>
Revised: July 6, 2023
Revised: August 20, 2023

The Moddable runtime integrates with XS to allow a multiple virtual machines to co-exist on a single microcontroller. The majority of projects use only a single virtual machine. However, there are situations where the several independent runtime contexts provided by having several virtual machines is advantageous. This isolation is useful to fully separate a particular set of scripts, for example user installed modules, from the core project functionality for security, privacy, and reliability reasons. Another useful situation is to allow scripts to perform blocking operations in one virtual machine while scripts in another virtual machine remain fully responsive. On microcontrollers with multiple CPU cores, workers may execute in parallel to take full advantage of the available CPU power.

Expand Down Expand Up @@ -33,10 +33,18 @@ To launch a worker, create an instance of the `Worker` class, passing the name o
The call to the `Worker` constructor returns only after execution of the specified module completes. If the worker module generates an exception during this step, an exception is propagated so that the call to `new Worker` throws an exception. This behavior means that the invoking virtual machine blocks until the new worker virtual machine has completed initialization. Consequently, any operations performed in a newly instantiated virtual machine should be relatively brief.

### Launching a worker with memory configuration
The previous example launches the worker with the default memory configuration. This may not be large enough for the worker, or may allocate more RAM than needed by the worker. An optional configuration object allows the script instantiating a new virtual machine to set the memory use.

let aWorker = new Worker("simpleworker",
{allocation: 8192, stackCount: 64, slotCount: 64});
The previous example launches the worker with the default memory creation configuration used for the main virtual machine. This may not be large enough for the worker, or may allocate more RAM than needed by the worker. An optional configuration object allows the script instantiating a new virtual machine to set the memory use.

```js
let aWorker = new Worker("simpleworker", {
static: 8192,
stack: 64,
heap: {
initial: 64,
incremental: 32
}
});
```

### Sending a message to a worker
Messages to workers are JavaScript objects and binary data.
Expand Down Expand Up @@ -98,12 +106,20 @@ The `Worker` constructor takes a path to the module used to initialize the new w

let aWorker = new Worker("simpleworker");

An optional dictionary contains configuration properties for the new worker. If the dictionary is not provided, the default parameters are used. These defaults vary by host runtime, so it is recommended to always provide a memory configuration.

let aWorker = new Worker("simpleworker",
{allocation: 8192, stackCount: 64, slotCount: 64});
An optional dictionary contains creation properties for the new worker. If the dictionary is not provided, the default parameters are used. These defaults vary by host runtime, so it is recommended to always provide a memory configuration. The creation properties are the same as the `creation` section of a manifest. See the [manifest documentation](../tools/manifest.md#creation) for details.

```js
let aWorker = new Worker("simpleworker", {
static: 8192,
stack: 64,
heap: {
initial: 64,
incremental: 32
}
});
```

The `allocation` property is the total memory shared by the new virtual machine for its chunk heap, slot heap, and stack. It is allocated when the virtual machine is created. The `stackCount` property is the number of slot entries on the virtual machine's stack. The `slotCount` property is the initial number of slots in the virtual machine's slot heap.
> **Note**: An earlier implementation of `Worker` used different properties to configure the memory creation. These have been deprecated and are no longer included in the documentation. It is recommended to update scripts to use the new format.
If an error occurs or an exception is thrown during execution of the module, the `Worker` constructor also throws an error.

Expand Down
19 changes: 15 additions & 4 deletions examples/base/worker/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2017 Moddable Tech, Inc.
* Copyright (c) 2016-2023 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK.
*
Expand All @@ -19,15 +19,26 @@ trace("hello\n");
let index = 0

function start() {
// note: allocation and stackCount are very small - most real workers will require a larger allocation and stackCount
let aWorker = new Worker("simpleworker", {allocation: 6 * 1024, stackCount: 64, slotCount: 32});
// note: chunk, heap (slots), and stack are very small - most real-world workers will require different settings
const aWorker = new Worker("simpleworker", {
static: 6 * 1024,
chunk: {
initial: 1536,
incremental: 256
},
heap: {
initial: 64,
incremental: 32
},
stack: 64
});

aWorker.postMessage({hello: "world", index: ++index});
aWorker.postMessage("hello, again");
aWorker.postMessage([1, 2, 3]);

aWorker.onmessage = function(message) {
if (3 == message.counter) {
if (3 === message.counter) {
trace(`start worker ${index}\n`);
aWorker.terminate();
start();
Expand Down
82 changes: 68 additions & 14 deletions modules/base/worker/modWorker.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Moddable Tech, Inc.
* Copyright (c) 2016-2023 Moddable Tech, Inc.
*
* This file is part of the Moddable SDK Runtime.
*
Expand Down Expand Up @@ -51,10 +51,7 @@ struct modWorkerRecord {
xsSlot owner;
xsSlot ownerPort;
xsSlot workerPort;
uint32_t allocation;
uint32_t stackCount;
uint32_t slotCount;
uint32_t keyCount;
xsCreation creation;
xsBooleanValue closing;
xsBooleanValue shared;
#ifdef INC_FREERTOS_H
Expand Down Expand Up @@ -136,6 +133,16 @@ void xs_worker_destructor(void *data)
}
}

static void getIntegerProperty(xsMachine *the, xsSlot *slot, xsIdentifier id, xsIntegerValue *value)
{
if (!xsmcHas(*slot, id))
return;

xsSlot tmp;
xsmcGet(tmp, *slot, id);
*value = xsmcToInteger(tmp);
}

static void workerConstructor(xsMachine *the, xsBooleanValue shared)
{
modCriticalSectionDeclare;
Expand Down Expand Up @@ -190,18 +197,65 @@ static void workerConstructor(xsMachine *the, xsBooleanValue shared)
}
}

xsCreation *creationP;
(void *)xsPreparationAndCreation(&creationP);
worker->creation = *creationP;
if (xsmcArgc > 1) {
xsmcGet(xsVar(0), xsArg(1), xsID_allocation);
worker->allocation = xsmcToInteger(xsVar(0));
if (xsmcHas(xsArg(1), xsID_allocation) ||
xsmcHas(xsArg(1), xsID_stackCount) ||
xsmcHas(xsArg(1), xsID_slotCount) ||
xsmcHas(xsArg(1), xsID_keyCount)) {
#ifdef mxDebug
xsTrace("deprecated worker creation parameters detected. update recommended.");
#endif
xsIntegerValue allocation, stackCount, slotCount, keyCount;
xsmcGet(xsVar(0), xsArg(1), xsID_allocation);
allocation = xsmcToInteger(xsVar(0));

xsmcGet(xsVar(0), xsArg(1), xsID_stackCount);
stackCount = xsmcToInteger(xsVar(0));

xsmcGet(xsVar(0), xsArg(1), xsID_slotCount);
slotCount = xsmcToInteger(xsVar(0));

xsmcGet(xsVar(0), xsArg(1), xsID_stackCount);
worker->stackCount = xsmcToInteger(xsVar(0));
xsmcGet(xsVar(0), xsArg(1), xsID_keyCount);
keyCount = xsmcToInteger(xsVar(0));

xsmcGet(xsVar(0), xsArg(1), xsID_slotCount);
worker->slotCount = xsmcToInteger(xsVar(0));
if (allocation)
worker->creation.staticSize = allocation;

xsmcGet(xsVar(0), xsArg(1), xsID_keyCount);
worker->keyCount = xsmcToInteger(xsVar(0));
if (stackCount)
worker->creation.stackCount = stackCount;

if (slotCount)
worker->creation.initialHeapCount = slotCount;

if (keyCount)
worker->creation.initialKeyCount = keyCount;
}
else {
xsIntegerValue value;

getIntegerProperty(the, &xsArg(1), xsID_static, &worker->creation.staticSize);
xsmcGet(xsVar(0), xsArg(1), xsID_chunk);
if (xsmcTest(xsVar(0))) {
getIntegerProperty(the, &xsVar(0), xsID_initial, &worker->creation.initialChunkSize);
getIntegerProperty(the, &xsVar(0), xsID_incremental, &worker->creation.incrementalChunkSize);
}
xsmcGet(xsVar(0), xsArg(1), xsID_heap);
if (xsmcTest(xsVar(0))) {
getIntegerProperty(the, &xsVar(0), xsID_initial, &worker->creation.initialHeapCount);
getIntegerProperty(the, &xsVar(0), xsID_incremental, &worker->creation.incrementalHeapCount);
}
getIntegerProperty(the, &xsArg(1), xsID_stack, &worker->creation.stackCount);
xsmcGet(xsVar(0), xsArg(1), xsID_keys);
if (xsmcTest(xsVar(0))) {
getIntegerProperty(the, &xsVar(0), xsID_initial, &worker->creation.initialKeyCount);
getIntegerProperty(the, &xsVar(0), xsID_incremental, &worker->creation.incrementalKeyCount);
getIntegerProperty(the, &xsVar(0), xsID_name, &worker->creation.nameModulo);
getIntegerProperty(the, &xsVar(0), xsID_symbol, &worker->creation.symbolModulo);
}
}
}

#ifdef INC_FREERTOS_H
Expand Down Expand Up @@ -367,7 +421,7 @@ int workerStart(modWorker worker)
xsMachine *the;
int result = 0;

the = modCloneMachine(worker->allocation, worker->stackCount, worker->slotCount, worker->keyCount, worker->module);
the = modCloneMachine(&worker->creation, worker->module);
if (!the)
return -1;

Expand Down
20 changes: 15 additions & 5 deletions tests/modules/base/worker/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ flags: [module]
import Worker from "worker";

const minimumOptions = {
allocation: 8192,
stackCount: 64,
slotCount: 64,
keyCount: 7
static: 8192,
heap: {
initial: 64,
incremental: 64
},
chunk: {
initial: 1024,
incremental: 512
},
stack: 64,
keys: {
initial: 1
}
};

assert.throws(SyntaxError, () => new Worker, "Worker constructor requires 1 argument");
Expand All @@ -19,7 +28,8 @@ assert.throws(TypeError, () => Worker("testworker"), "Worker constructor called
assert.throws(Error, () => new Worker("invalid module specifier", minimumOptions), "Worker constructor with invalid module specifier");
assert.throws(Error, () => new Worker(12, minimumOptions), "Worker constructor with invalid module specifier");

assert.throws(Error, () => new Worker("testworker", {...minimumOptions, allocation: 1024 * 1024 * 1024}), "Worker with 1 GB alocation should fail on microcontroller");
assert.throws(Error, () => new Worker("testworker", {allocation: 1024 * 1024 * 1024, stackCount: 64, slotCount: 64, keyCount: 7}), "Worker with 1 GB alocation should fail on microcontroller (deprecated property names)");
assert.throws(Error, () => new Worker("testworker", {...minimumOptions, static: 1024 * 1024 * 1024}), "Worker with 1 GB alocation should fail on microcontroller");
assert.throws(Error, () => new Worker("testthrowworker", minimumOptions), "Worker module throws");

new Worker("testworker", minimumOptions)
Expand Down
26 changes: 26 additions & 0 deletions tests/modules/base/worker/constructor_deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*---
description:
flags: [module]
---*/

import Worker from "worker";

const minimumOptions = {
allocation: 8192,
stackCount: 64,
slotCount: 64,
keyCount: 7
};

assert.throws(SyntaxError, () => new Worker, "Worker constructor requires 1 argument");
assert.throws(TypeError, () => new Worker(Symbol()), "Worker constructor rejects Symbol");
assert.throws(TypeError, () => Worker("testworker"), "Worker constructor called as function");

assert.throws(Error, () => new Worker("invalid module specifier", minimumOptions), "Worker constructor with invalid module specifier");
assert.throws(Error, () => new Worker(12, minimumOptions), "Worker constructor with invalid module specifier");

assert.throws(Error, () => new Worker("testworker", {...minimumOptions, allocation: 1024 * 1024 * 1024}), "Worker with 1 GB alocation should fail on microcontroller");
assert.throws(Error, () => new Worker("testthrowworker", minimumOptions), "Worker module throws");

new Worker("testworker", minimumOptions)

17 changes: 13 additions & 4 deletions tests/modules/base/worker/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@ flags: [module, async]
import Worker from "worker";

const minimumOptions = {
allocation: 8192,
stackCount: 64,
slotCount: 64,
keyCount: 7
static: 8192,
heap: {
initial: 64,
incremental: 64
},
chunk: {
initial: 1024,
incremental: 512
},
stack: 64,
keys: {
initial: 1
}
};

const messages = [
Expand Down
Loading

0 comments on commit 1614168

Please sign in to comment.