coincident

repository·main·Indexed 42 days ago

https://github.com/webreflection/coincident

An Atomics-based Proxy library designed to simplify and synchronize tasks between the main thread and Web Workers. It enables high-performance, often synchronous, communication using SharedArrayBuffers and optimized data transfer. Version 4.1.1 supports synchronous access to the main thread window from Workers and provides utilities for instrumenting WebSockets in Node.js or Bun via coincident/server.

Tokens
5.5K
Snippets
18
Records
22
Agent score
52%

What's inside coincident

  1. Use asynchronous-only mode for SharedArrayBuffer

    main

    If you cannot use special headers or a ServiceWorker, coincident defaults to a SharedArrayBuffer polyfill that only supports asynchronous notifications.

    This mode is ideal when:

    • No special headers or ServiceWorkers are allowed.
    • You only need to await asynchronous utilities from a Worker.
    • You never need to reach or execute code on the main thread from a Worker.
    • Your Worker never receives a main thread reference as an argument.
  2. How Coincident V4 workers and proxies work together

    main

    Coincident uses an Atomics-based Proxy to synchronize tasks between the main thread and Workers.

    1. On the Main Thread: You use coincident/main to create a Worker instance. This instance provides a proxy where you can assign callbacks (e.g., proxy.location = () => location.href) that the Worker can then invoke.
    2. On the Worker Thread: You use coincident/worker to access the proxy. This proxy allows you to call functions exposed by the main thread. If native is true (meaning SharedArrayBuffer is supported), these calls can be synchronous.
    3. Data Transfer: Use the transfer utility at the end of proxied function calls to move ArrayBuffers directly via postMessage for better performance.
  3. Avoid deadlocks when invoking cross-world utilities

    main

    Deadlocks can occur when a Worker invokes a synchronous utility on the Main thread using Atomics.wait, and that Main utility subsequently tries to invoke a synchronous utility on that same Worker.

    Best Practice: Do not invoke utilities from the other world while one of your own exported utilities is currently executing. If you must, execute the foreign utility on the next tick, micro-task, timeout, or idle state.

  4. Enable SharedArrayBuffer (SAB) for synchronous features

    main

    To enable synchronous Atomics and full SharedArrayBuffer capabilities, you must ensure your server provides the necessary security headers.

    Ways to enable SAB:

    1. Server Headers: Configure your server to provide the required COI headers (e.g., using npx mini-coi ./ for local testing).
    2. ServiceWorker Bootstrap: Include <script src="./mini-coi.js"></script> at the top of your <head> to use a ServiceWorker that forces these headers for all requests.
    3. Coincident ServiceWorker: Pass a ServiceWorker file to the coincident constructor via the serviceWorker option (e.g., npx coincident ./public/sw.js) to polyfill the synchronous case.
  5. Optimize performance by caching window references

    main

    Every property access via the window reference involves a Worker ↔ Main thread roundtrip. To avoid performance degradation, especially when using a SharedArrayBuffer polyfill, manually cache references that you know will not change.

    Performance Note:

    • With SharedArrayBuffer polyfill: ~1,000 roundtrips/sec.
    • With native SharedArrayBuffer: ~50,000 roundtrips/sec.
    const { window } = await coincident();
    
    // Instead of accessing window.document.body repeatedly:
    const { document } = window;
    const { head, body } = document;
    
    // Use the cached reference
    body.textContent = 'Hello World 👋';
  6. Use XMLHttpRequest synchronous fallback

    main

    If header issues prevent SharedArrayBuffer from working (e.g., in iframes), you can use a ServiceWorker-based fallback that intercepts synchronous xhr calls.

    Setup:

    1. Generate the fallback file: npx coincident ./sw.js.
    2. Initialize coincident with the generated file: { serviceWorker: './sw.js' }.

    Note: This method is slower and may face rate-limiting or kill-switch issues on iOS devices.

    // Initialization with XHR fallback
    const { Worker } = await coincident({ serviceWorker: './sw.js' });
  7. Initialize coincident/server/main on the main thread

    main

    When your Worker code requires references to both the main window and the server, use coincident/server/main on the main thread. Unlike the standard window variant, you must provide the WebSocket ws URL during initialization.

    import coincident from 'coincident/server/main';
    
    const { Worker, polyfill, transfer } = coincident({
      ws: 'ws://localhost:8080/'
    });
  8. Use coincident/server/worker to access server utilities

    main

    On the Worker side, importing coincident/server/worker provides a namespace that includes utilities to interact with the server. After bootstrap, the returned object contains a server proxy (a synchronous, Atomics.wait based proxy to the server's globalThis) and an isServerProxy helper.

    It also provides an ffi (Foreign Function Interface) namespace for calling methods on both window and server.

    import coincident from 'coincident/server/worker';
    
    const {
      proxy, native, transfer,
      window, isWindowProxy,
      server,
      isServerProxy,
      ffi: {
        assign, gather, query, evaluate,
        window: { ... },
        server: { ... },
      }
    } = await coincident();
    
    // Direct synchronous access to the server
    server.console.log('Hello World 👋');
    
    // Example of module import on the server
    const os = await server.import('os');
    console.log(os.platform());
  9. Instrument WebSockets with coincident/server

    main

    Use coincident/server to instrument connected WebSockets in Node.js, Bun, or other environments. This allows a Worker to drive both the main thread and the running server.

    Warning: This feature enables a Worker to potentially take over the server; it is intended primarily for Kiosk or IoT projects and should not be exposed to untrusted public code.

    // Bun example
    import coincident from 'coincident/server';
    
    serve({
      port,
      fetch,
      websocket: coincident({ bun: true })
    });
    
    // NodeJS with `ws` module
    import { WebSocketServer } from 'ws';
    import coincident from 'coincident/server';
    
    const server = ...;
    coincident({
      wss: new WebSocketServer({ server })
    });
  10. Initialize Coincident V4 in a Worker

    main

    To use Coincident within a Worker, import coincident from coincident/worker. This provides a proxy to invoke main-thread utilities (sync or async) and a direct method to transfer values as-is. You can configure minByteLength and maxByteLength for SharedArrayBuffer usage.

    import coincident from 'coincident/worker';
    
    const {
      // proxy to invoke main thread utilities
      proxy: Proxy,
      // true if SharedArrayBuffer and sync operations are usable
      native: boolean,
      // utility to transfer buffers directly via `postMessage`
      transfer: (...buffers: ArrayBuffer[]) => buffers,
      // a way to directly transfer a value as it is
      direct: value => value,
    } = await coincident({
      transform: value => any,
      decoder: reflectedFFIDirectDecoder,
      transfer: boolean,
      minByteLength: 0x7FFF,
      maxByteLength: 0x1000000,
    });
  11. Initialize Coincident V4 on the Main Thread

    main

    To use Coincident on the main thread, import coincident from coincident/main. This returns an object containing a proxied Worker class, a native boolean (indicating if SharedArrayBuffer and sync operations are available), and a transfer utility for optimizing buffer transfers via postMessage.

    import coincident from 'coincident/main';
    
    const {
      // The Worker class (extends global Worker and adds .proxy)
      Worker: globalThis.Worker & { proxy: Proxy },
      // true if SharedArrayBuffer and sync operations are usable
      native: boolean,
      // utility to transfer buffers directly via `postMessage`
      transfer: (...buffers: ArrayBuffer[]) => buffers,
    } = coincident({
      // optional: transform values before sending
      transform: value => any,
      // optional: custom encoder (defaults to reflected-ffi/encoder)
      encoder: reflectedFFIEncoder,
      // optional: disable transfer ability for performance boost
      transfer: boolean,
    });
  12. Access the Main Thread Window from a Worker

    main

    If a Worker needs to operate directly on the main thread's DOM or window object, use coincident/window/worker. This provides a window proxy that allows synchronous access to the main thread's globalThis using Atomics.wait.

    import coincident from 'coincident/window/worker';
    
    const {
      proxy,
      native,
      transfer,
      // synchronous, Atomic.wait based Proxy to the main thread's globalThis
      window,
      // returns true if the reference points to the main thread
      isWindowProxy,
    } = await coincident();
    
    // Direct synchronous access
    console.log(window.location.href);
    window.document.body.textContent = 'Hello World 👋';