denoland/deno · error · TypeError

Cannot construct UnsafeCallback: cannot be nonblocking

Error message

Cannot construct UnsafeCallback: cannot be nonblocking

What it means

Deno.UnsafeCallback registers a native-to-JS trampoline for passing JS functions to FFI. Callbacks are invoked synchronously on whatever native thread calls them, so the nonblocking flag (which exists for outgoing calls made via UnsafeFnPointer/UnsafePointerFunction) has no meaning here and is rejected with a TypeError at construction.

Source

Thrown at ext/ffi/00_ffi.js:405

    case "isize":
      return [8, 8];
    default:
      throw new TypeError(`Cannot get pointer size, unsupported type: ${type}`);
  }
}

class UnsafeCallback {
  #refcount;
  // Internal promise only meant to keep Deno from exiting
  #refpromise;
  #rid;
  definition;
  callback;
  pointer;

  constructor(definition, callback) {
    if (definition.nonblocking) {
      throw new TypeError(
        "Cannot construct UnsafeCallback: cannot be nonblocking",
      );
    }
    const { 0: rid, 1: pointer } = op_ffi_unsafe_callback_create(
      definition,
      callback,
    );
    this.#refcount = 0;
    this.#rid = rid;
    this.pointer = pointer;
    this.definition = definition;
    this.callback = callback;
  }

  static threadSafe(definition, callback) {
    const unsafeCallback = new UnsafeCallback(definition, callback);
    unsafeCallback.ref();
    return unsafeCallback;

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Remove nonblocking from the callback definition
  2. Keep separate definition constants for calls and for callbacks so flags do not leak across
  3. If the callback body blocks, make the JS side dispatch work asynchronously (e.g. queue a task) instead of using the flag

Example fix

// before
new Deno.UnsafeCallback({ parameters: [], result: "void", nonblocking: true }, cb);

// after
new Deno.UnsafeCallback({ parameters: [], result: "void" }, cb);
Defensive patterns

Strategy: validation

Validate before calling

function makeUnsafeCallback(parameters, result, cb) {
  const definition = { parameters, result }; // never carries nonblocking
  return new Deno.UnsafeCallback(definition, cb);
}

Type guard

function isCallbackDefinition(def: {
  parameters: unknown[];
  result: unknown;
  nonblocking?: boolean;
}): boolean {
  return def.nonblocking !== true;
}

Try / catch

try {
  cb = new Deno.UnsafeCallback(definition, handler);
} catch (err) {
  if (err instanceof TypeError && err.message === "Cannot construct UnsafeCallback: cannot be nonblocking") {
    const { nonblocking: _, ...rest } = definition;
    cb = new Deno.UnsafeCallback(rest, handler);
  } else throw err;
}

Prevention

When it happens

Trigger: new Deno.UnsafeCallback({ parameters: ["u32"], result: "void", nonblocking: true }, cb).

Common situations: Reusing one shared definition object for both dlsym calls (where nonblocking is valid) and callbacks; copy-pasting a call definition into a callback registration; assuming all definition flags apply to every FFI API.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/e043942e10636067. Report an issue: GitHub.