Skip to main content

SharedArrayBuffer & Cross-Origin Isolation

SharedArrayBuffer is a powerful primitive for true memory sharing between JavaScript contexts, but it requires strict security measures due to Spectre-class timing attacks.

Security Requirements

COOP/COEP Headers

SharedArrayBuffer is only available in cross-origin isolated contexts:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Checking Isolation Status

// Check if SharedArrayBuffer is available
if (typeof SharedArrayBuffer !== 'undefined' && crossOriginIsolated) {
console.log('SharedArrayBuffer is available');
} else {
console.log('SharedArrayBuffer is NOT available');
console.log('Cross-origin isolated:', crossOriginIsolated);
}
Security Trade-off Enabling cross-origin isolation prevents loading

resources from other origins unless they explicitly opt-in with CORP headers. This can break third-party integrations. :::

Usage Patterns

Basic Memory Sharing

// Main thread
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);

// Send to worker
worker.postMessage({ sharedBuffer });

// Worker thread
self.onmessage = ({ data: { sharedBuffer } }) => {
const sharedArray = new Int32Array(sharedBuffer);

// Direct memory access - no copying!
sharedArray[0] = 42;
};

Atomics for Synchronization

// Producer
const buffer = new SharedArrayBuffer(16);
const view = new Int32Array(buffer);

// Atomic operations prevent race conditions
Atomics.store(view, 0, 123);
Atomics.notify(view, 0, 1); // Wake waiting workers

// Consumer
Atomics.wait(view, 0, 0); // Wait for change
const value = Atomics.load(view, 0);

Node.js Differences

// Node.js: SharedArrayBuffer available by default
import { Worker, isMainThread, parentPort } from 'worker_threads';

if (isMainThread) {
const sharedBuffer = new SharedArrayBuffer(1024);
const worker = new Worker(__filename, {
workerData: { sharedBuffer },
});
} else {
const { sharedBuffer } = require('worker_threads').workerData;
// Use shared buffer...
}

WASM Integration

// Rust side - using shared memory
use wasm_bindgen::prelude::*;
use js_sys::SharedArrayBuffer;

#[wasm_bindgen]
pub struct SharedProcessor {
memory: SharedArrayBuffer,
}

#[wasm_bindgen]
impl SharedProcessor {
#[wasm_bindgen(constructor)]
pub fn new(memory: SharedArrayBuffer) -> SharedProcessor {
SharedProcessor { memory }
}

pub fn process(&self) -> Result<(), JsValue> {
// Process data directly in shared memory
Ok(())
}
}
// TypeScript side
const sharedBuffer = new SharedArrayBuffer(1024 * 1024);
const processor = new SharedProcessor(sharedBuffer);

// Multiple workers can operate on same memory
workers.forEach((worker) => {
worker.postMessage({
type: 'init',
sharedBuffer,
processor: processor.ptr, // WASM instance pointer
});
});

Performance Considerations

Memory Access Patterns

// ❌ Bad: Random access patterns
for (let i = 0; i < array.length; i++) {
array[Math.floor(Math.random() * array.length)] = i;
}

// ✅ Good: Sequential access patterns
for (let i = 0; i < array.length; i++) {
array[i] = i;
}

Cache Line Awareness

// Each worker operates on separate cache lines (64 bytes)
const CACHE_LINE_SIZE = 64;
const WORKER_OFFSET = CACHE_LINE_SIZE / 4; // 16 Int32 elements

workers.forEach((worker, index) => {
const startIndex = index * WORKER_OFFSET;
worker.postMessage({ startIndex, length: WORKER_OFFSET });
});

Common Gotchas

1. Detection vs Availability

// ❌ Wrong: Just checking existence
if (typeof SharedArrayBuffer !== 'undefined') {
// May still throw if not cross-origin isolated!
}

// ✅ Correct: Check both existence and isolation
function canUseSharedArrayBuffer(): boolean {
return (
typeof SharedArrayBuffer !== 'undefined' && crossOriginIsolated === true
);
}

2. Resource Loading with COEP

// ❌ Will fail with COEP: require-corp
fetch('https://external-api.com/data.json');

// ✅ Need CORP header or use credentials: 'omit'
fetch('https://external-api.com/data.json', {
mode: 'cors',
credentials: 'omit',
});

3. Memory Growth

// ❌ SharedArrayBuffer cannot grow
const buffer = new SharedArrayBuffer(1024);
// buffer.grow(2048); // ❌ This doesn't exist!

// ✅ Pre-allocate sufficient memory or reallocate
function reallocateShared(oldBuffer: SharedArrayBuffer, newSize: number) {
const newBuffer = new SharedArrayBuffer(newSize);
const oldView = new Uint8Array(oldBuffer);
const newView = new Uint8Array(newBuffer);
newView.set(oldView);
return newBuffer;
}

Signals of Mastery

  • Understands COOP/COEP requirement and trade-offs
  • Can explain the Spectre vulnerability connection
  • Knows difference between Node.js and browser availability
  • Uses Atomics correctly for synchronization
  • Considers cache line alignment for performance
  • Handles resource loading restrictions with COEP

Red Flags

  • Assumes SharedArrayBuffer is always available
  • Doesn't understand cross-origin isolation requirements
  • Uses SharedArrayBuffer without Atomics for synchronization
  • Ignores COEP impact on third-party resources
  • Confuses SharedArrayBuffer with regular ArrayBuffer transfer