denoland/deno · error · DOMExceptionOperationError
validation error occurred
Error message
validation error occurred
What it means
GPUBuffer.mapAsync() in Deno's WebGPU maps the buffer via wgpu_core::buffer_map_async; if that call returns a validation error, buffer.rs maps it to BufferError::Operation("validation error occurred") and rejects the mapAsync promise (the error is also pushed to the device's uncaptured-error handler). Typical validation failures: offset/size not aligned, size out of bounds, buffer created without MAP usage, or the buffer/device state being invalid.
Source
Thrown at ext/webgpu/buffer.rs:235
sender.send(status).unwrap();
});
let err = self
.instance
.buffer_map_async(
self.id,
offset,
size,
wgpu_core::resource::BufferMapOperation {
host: mode,
callback: Some(callback),
},
)
.err();
if err.is_some() {
self.error_handler.push_error(err);
return Err(BufferError::Operation("validation error occurred"));
}
}
let done = Rc::new(RefCell::new(false));
let done_ = done.clone();
let device_poll_fut = async move {
while !*done.borrow() {
{
self
.instance
.device_poll(self.device, wgpu_types::PollType::wait_indefinitely())
.unwrap();
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
Ok::<(), BufferError>(())
};
View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Create the buffer with the right flags: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST for readback, GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC for upload.
- Align offset and size to 8 bytes (round down/up) and clamp size to buffer.byteLength - offset.
- Ensure the buffer is unmapped (getMappedRange + unmap finished) before calling mapAsync again.
- Catch the rejection, and also register device.onuncapturederror / device.lost handling to surface the underlying wgpu validation message.
Example fix
// before
const buf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.COPY_DST, // no MAP_READ -> "validation error occurred"
});
await buf.mapAsync(GPUMapMode.READ, 0);
// after
const buf = device.createBuffer({
size: 16,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
await buf.mapAsync(GPUMapMode.READ, 0, 16); Defensive patterns
Strategy: try-catch
Validate before calling
const MAP_ALIGNMENT = 8;
function assertMappable(buffer, offset, size) {
const usage = buffer.usage ?? (buffer._creationUsage as GPUBufferUsageFlags);
const s = size ?? buffer.byteLength - offset;
if (offset % MAP_ALIGNMENT !== 0 || s % MAP_ALIGNMENT !== 0) {
throw new Error(`mapAsync offset/size must be ${MAP_ALIGNMENT}-byte aligned`);
}
if (offset + s > buffer.byteLength) throw new Error("mapAsync range out of bounds");
return s;
} Type guard
function isMappableBuffer(buffer: GPUBuffer, mode: GPUMapModeFlags): boolean {
const u = buffer.usage;
return mode === GPUMapMode.READ
? (u & GPUBufferUsage.MAP_READ) !== 0
: (u & GPUBufferUsage.MAP_WRITE) !== 0;
} Try / catch
try {
await buffer.mapAsync(GPUMapMode.READ, alignedOffset, alignedSize);
} catch (err) {
console.error("mapAsync failed:", err.message); // "validation error occurred"
// recreate buffer with MAP flags / fix alignment, or bail
}
device.onuncapturederror = (e) => console.error("uncaptured:", e.error.message); Prevention
- Always create readback buffers with MAP_READ | COPY_DST and upload buffers with MAP_WRITE | COPY_SRC.
- Align map offsets/sizes to 8 bytes; do not reuse 4-byte copyBufferToBuffer math for mapping.
- Never call mapAsync while a previous map is pending; unmap before remapping.
- Log device uncaptured errors to see the underlying wgpu validation reason behind the generic message.
When it happens
Trigger: `await buffer.mapAsync(GPUMapMode.READ, offset, size)` where offset or size is not a multiple of 8, offset+size exceeds buffer.byteLength, the GPUBuffer was created without GPUBufferUsage.MAP_READ/MAP_WRITE, mapAsync is called while already mapped/pending, or the device was lost/destroyed. Example: creating a buffer with COPY_DST only and then trying to map it for reading.
Common situations: Forgetting MAP_READ|MAP_WRITE usage flags and mapping anyway (most common); passing byte offsets computed from unaligned struct sizes; reusing offset math meant for copyBufferToBuffer (4-byte alignment) where mapping requires 8; racing two mapAsync calls on the same buffer; continuing after device loss in error-handling tests.
Related errors
- BenchContext::start() has already been invoked
- ${prefix}Linter plugin name must only contain lowercase lett
- ${prefix}Linter plugin name must start and end with a lowerc
- ${prefix}Linter plugin name must not have consequtive hyphen
- ${prefix}Linter plugin rules must be an object
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/89206f72ab0b561e.
Report an issue: GitHub.