Skip to content

Commit 28c3dfa

Browse files
committed
Add more comprehensive details about mini-coi and service-worker options when using workers.
1 parent 9528b18 commit 28c3dfa

2 files changed

Lines changed: 147 additions & 27 deletions

File tree

docs/faq.md

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,41 +58,36 @@ This is the most common error new PyScript users face:
5858

5959
#### When
6060

61-
This error occurs when code running in a worker tries to access `window`
62-
or `document` objects that exist on the main thread.
61+
This error occurs when code running in a worker tries to access
62+
`window` or `document` objects from the main thread.
6363

64-
The error indicates either **your web server is incorrectly configured**
65-
or **a `service-worker` attribute is missing from your script element**.
64+
It indicates one of three situations:
6665

67-
Specifically, one of three situations applies:
66+
1. **Server misconfiguration:** Your script has a `worker` attribute and
67+
accesses `window` or `document`, but your web server isn't configured to
68+
enable SharedArrayBuffer/Atomics.
6869

69-
Your web server configuration prevents the browser from enabling Atomics
70-
(a technology for cross-thread communication). When your script element
71-
has a `worker` attribute and your Python code uses `window` or
72-
`document` objects that exist on the main thread, this browser
73-
limitation causes failure unless you reconfigure your server.
70+
2. **Missing service-worker attribute:** You're using
71+
`<script type="py-editor">` (which always runs in a worker) without
72+
providing a `service-worker` attribute for fallback synchronous operations.
7473

75-
You're using `<script type="py-editor">` (which always runs in a worker)
76-
without providing a fallback via a `service-worker` attribute on that
77-
element.
74+
3. **PyWorker without fallback:** You've created a `PyWorker` or `MPWorker`
75+
instance without providing a `service_worker` fallback (see:
76+
[this API class](./api/context.md/#pyscript.context.PyWorker)).
7877

79-
You've explicitly created a `PyWorker` or `MPWorker` instance somewhere
80-
in your code without providing a `service_worker` fallback.
81-
82-
The [workers guide](./user-guide/workers.md) documents all these cases
78+
The [workers guide](./user-guide/workers.md) documents all these use cases
8379
with code examples and solutions.
8480

8581
#### Why
8682

87-
For `document.getElementById('some-id').value` to work in a worker,
88-
JavaScript requires two primitives:
89-
90-
[SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
91-
allows multiple threads to read and write shared memory.
83+
Workers often require two browser based features to access main thread objects
84+
like `window` and `document`.
9285

93-
[Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)
94-
provides `wait(sab, index)` and `notify(sab, index)` to coordinate
95-
threads, where `sab` is a SharedArrayBuffer.
86+
1. [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
87+
allows multiple threads to read and write shared memory.
88+
2. [Atomics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)
89+
provides `wait(sab, index)` and `notify(sab, index)` to coordinate
90+
threads, where `sab` is a SharedArrayBuffer.
9691

9792
Whilst a worker waits for a main thread operation, it doesn't consume
9893
CPU. It idles until the referenced buffer index changes, effectively
@@ -105,8 +100,124 @@ responsive during heavy computation.
105100

106101
Unfortunately, we cannot patch or work around these primitives - they're
107102
defined by web standards. However, various solutions exist for working
108-
within these limitations. The [workers guide](./user-guide/workers.md)
109-
explains how.
103+
within these limitations.
104+
105+
The easiest fix is to ensure your webserver is correctly configured to
106+
serve responses with the following headers:
107+
108+
```
109+
Access-Control-Allow-Origin: *
110+
Cross-Origin-Opener-Policy: same-origin
111+
Cross-Origin-Embedder-Policy: require-corp
112+
Cross-Origin-Resource-Policy: cross-origin
113+
```
114+
115+
If you are unable to reconfigure your webserver (perhaps, for example,
116+
you're using a third party for hosting), you have two options:
117+
118+
**Option 1:** `mini-coi`
119+
120+
The `mini-coi` project ensures a browser's
121+
[complicated Cross Origin Isolation (COI) settings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements)
122+
"just work" for web workers. This is especially useful when you have no
123+
control over the HTTP headers returned by the server.
124+
125+
For performance reasons, this is the preferred option so Atomics works at
126+
native speed.
127+
128+
The simplest way to use mini-coi is to copy the
129+
[`mini-coi.js`](https://raw.githubusercontent.com/WebReflection/mini-coi/main/mini-coi.js)
130+
file to the root of your website (i.e. `/`), and reference it as the first
131+
child tag in the `<head>` of your HTML documents:
132+
133+
```html
134+
<html>
135+
<head>
136+
<script src="/mini-coi.js"></script>
137+
<!-- etc -->
138+
</head>
139+
<!-- etc -->
140+
</html>
141+
```
142+
143+
**Option 2:** `service-worker` attribute
144+
145+
This allows you to slot in a custom
146+
[service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
147+
to handle requirements for synchronous operations.
148+
149+
Each `<script type="m/py">` or `<m/py-script>` may optionally have a
150+
`service-worker` attribute pointing to a locally served file (the same way
151+
`mini-coi.js` needs to be served).
152+
153+
This file needs to orchestrate _coincident POST_ requests so that:
154+
155+
* You can copy and paste
156+
[the coincident Service Worker](https://cdn.jsdelivr.net/npm/coincident/dist/sw.js)
157+
into your local project and point at that via the `service-worker`
158+
attribute. This will not change the original behavior of your project, it
159+
will not interfere with the default or pre-defined headers your application
160+
uses already but it will **fallback to a (slower but working) synchronous
161+
operation** that allows both `window` and `document` access in your worker
162+
logic.
163+
* You can import
164+
[coincident listeners](https://github.com/WebReflection/coincident/blob/main/src/sabayon/listeners.js)
165+
in your project and at least add the `fetch` one before your listeners. It
166+
will automatically stop propagation when a request is meant to be handled
167+
so that the rest of your logic will not be affected or change by any mean.
168+
169+
```html
170+
<html>
171+
<head>
172+
<!-- PyScript link and script -->
173+
</head>
174+
<body>
175+
<script type="py" service-worker="./sw.js" worker>
176+
from pyscript import window, document
177+
178+
document.body.append("Hello PyScript!")
179+
</script>
180+
</body>
181+
</html>
182+
```
183+
184+
!!! warning
185+
186+
Using _coincident_ as the fallback for synchronous operations via Atomics
187+
should be **the last solution to consider**. It is inevitably slower than
188+
using native Atomics.
189+
190+
If you must use `service-worker` attribute, always reduce the amount of
191+
synchronous operations by caching references from the _main_ thread.
192+
193+
```python
194+
# ❌ THIS IS UNNECESSARILY SLOWER
195+
from pyscript import document
196+
197+
# add a data-test="not ideal attribute"
198+
document.body.dataset.test = "not ideal"
199+
# read a data-test attribute
200+
print(document.body.dataset.test)
201+
202+
# - - - - - - - - - - - - - - - - - - - - -
203+
204+
# ✔️ THIS IS FINE
205+
from pyscript import document
206+
207+
# if needed elsewhere, reach it once
208+
body = document.body
209+
dataset = body.dataset
210+
211+
# add a data-test="not ideal attribute"
212+
dataset.test = "not ideal"
213+
# read a data-test attribute
214+
print(dataset.test)
215+
```
216+
217+
In latter example the number of operations has been reduced from six to
218+
just four. The rule of thumb is: _if you ever need a DOM reference more
219+
than once, cache it_.
220+
110221

111222
### Borrowed proxy
112223

docs/user-guide/workers.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ your application remains responsive even during heavy processing.
3535
you make use of the [mini-coi](https://github.com/WebReflection/mini-coi)
3636
project (see their README for details).
3737

38+
If you encounter problems with errors relating to the
39+
SharedArrayBuffer (a core component for helping to make workers easy to
40+
use in certain contexts), please see the
41+
[extensive help in our FAQ](../faq.md/#sharedarraybuffer).
42+
3843
## Defining workers
3944

4045
Workers are defined with `<script>` tags that have a `worker` attribute:
@@ -156,6 +161,10 @@ the worker. The `config` parameter accepts a configuration dictionary or JSON
156161
string (optional). The `type` parameter specifies the interpreter: `"py"`
157162
(default) or `"mpy"` (optional).
158163

164+
It is possible to directly and manually instantiate a
165+
[PyWorker class](../api/context.md/#pyscript.context.PyWorker), although this
166+
is discouraged in favour of the managed `create_named_worker` method.
167+
159168
## Configuration
160169

161170
Workers support the same configuration as main thread scripts. You can specify

0 commit comments

Comments
 (0)