6

I am facing a problem with decode() on SharedArrayBuffer.

Code:

  var sharedArrayBuffer = new SharedArrayBuffer(2);
  var uint8Array = new Uint8Array(sharedArrayBuffer);
  uint8Array[0] = 20;
  var decoder = new TextDecoder();
  decoder.decode(uint8Array);

Error:

Failed to execute 'decode' on 'TextDecoder': The provided ArrayBufferView value must not be shared.

There is a specification here that warns developers about race-condition on this type of memory. Can I somehow force decoding? I am sure data will not be changed during decoding. Or is there a workaround for this?

Reasons: I want to create a single copy of Uint8Array and pass it without copying via postmessage (which copies by default if transferrable is not specified) to several(>3) IFrames(with sandbox tag). Maybe there are other possible solutions?

moraprex
  • 103
  • 9
  • Your pages are same-origin anyway, right? So can't you just do `top.globallyAccessibleArrayBuffer` from your frames? – Kaiido Jul 12 '22 at 08:12
  • I am missing something, is there a way IFrame with tag sandbox can access its parents' variables? Or can I ask you to elaborate on your comment(provide any code)? – moraprex Jul 12 '22 at 08:30
  • You never stated these iframes were sandboxed befire my comment. How do you make your CO isolation work? With the experimental `allow=cross-origin-isolated` feature policy? Or with `sandbox=allow-same-origin`? Or with something else I'm not aware of? I was under the impression you need same-origin to have SharedArrayBuffer, and thus you'd have access to the various contexts anyway. – Kaiido Jul 12 '22 at 09:32
  • Sorry, I changed the description after your comment, totally missed it when describing the problem. I use "Cross-Origin-Opener-Policy" : "same-origin", "Cross-Origin-Embedder-Policy" : "require-corp" in headers of my webpack dev server in order to use SharedArrayBuffer. – moraprex Jul 12 '22 at 10:08
  • Interesting... What browser are you using? My main one is Firefox and because of it I thought its behavior was the "normal" one, and there, I am unable to load a sandboxed iframe from a cross-origin isolated document, unless it has the "allow-same-origin" sandbox clause. I now see that both Chrome and Safari do allow it though, but then, only in Safari am I able to postMessage an SAB, Chrome failing silently (no error, no message). So given all this it seems that to have a cross-browser behavior you'd need the iframes to be same-origin anyway. But maybe you're targeting only one vendor? – Kaiido Jul 12 '22 at 12:38
  • Actually Chrome does fire a "messageerror" on the iframe when postMessaging the SAB. – Kaiido Jul 12 '22 at 23:58
  • Currently, I am using Chrome and I did not even think that the behavior might be different amongst different browsers, wow. For now, I am still unable to use existing TextDecoder on SAB unless I copy values from SAB to AB (f.e: Uint8Array) and pass it to TextDecoder.decode(). – moraprex Jul 13 '22 at 06:56

3 Answers3

0

Sadly, today you have to incur the extra buffer + copy in order to consistently do this across browsers. It's plausible that any given browser may allow this, but it isn't portable as of writing this.

For context, the ability for TextDecoder.decode(...) to take a SharedArrayBuffer is desired functionality for WebAssembly and other low-level performance scenarios, but it's stuck in standards and implementation-related discussions.

Here's an example of a buffer-intermediate code path that works to bypass this failing today:

function decodeFromSharedBuffer(sharedBuffer, maxLength) {
  const decoder = new TextDecoder()
  const copyLength = Math.min(sharedBuffer.byteLength, maxLength)

  // Create a temporary ArrayBuffer and copy the contents of the shared buffer
  // into it.
  const tempBuffer = new ArrayBuffer(copyLength)
  const tempView = new Uint8Array(tempBuffer)

  let sharedView = new Uint8Array(sharedBuffer)
  if (sharedBuffer.byteLength != copyLength) {
    sharedView = sharedView.subarray(0, copyLength)
  }
  tempView.set(sharedView)

  return decoder.decode(tempBuffer)
}
akdom
  • 32,264
  • 27
  • 73
  • 79
-1

Looks like using ArrayBuffer works fine.

  var arrayBuffer = new ArrayBuffer(2);
  var uint8Array = new Uint8Array(arrayBuffer);
  uint8Array[0] = 20;
  var decoder = new TextDecoder();
  decoder.decode(uint8Array);
Anton
  • 1,401
  • 8
  • 12
  • The problem is - I would like then share this memory to IFrames, I do want to evade of structured cloning during postmessage, thus I am using SharedArrayBuffer. – moraprex Jul 12 '22 at 07:02
  • ah, right. Looks like you have to use Atomics in this case. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics#using_atomics – Anton Jul 12 '22 at 07:15
  • Am I right that I have to implement decoding by myself using atomics for SharedArrayBuffer? – moraprex Jul 12 '22 at 08:52
-1

Js

var sharedArrayBuffer = new SharedArrayBuffer(32);
var ary = new Int32Array(sharedArrayBuffer);
ary[0] = 20;
var encoder = new TextEncoder();
var EncodedArray = encoder.encode(ary);
console.log("Encoded Array : ", EncodedArray)
var decoder = new TextDecoder();
var DecodedArray = decoder.decode(EncodedArray);
console.log("Decoded Array is : ", DecodedArray)
  • Sir, I do not want to encode my SharedArrayBuffer, the data in it is already encoded. I only need to decode it, thanks. Whilst your approach - while encoding it first returns a complete new Uint8Array which is ArrayBuffer underneath and hence can be passed to decode() function later. So, your answer is not what I am looking for. – moraprex Jul 13 '22 at 06:59
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 13 '22 at 12:29