{"record":{"id":"b3272e0a49bd81a6","repo":"GraphiteEditor/Graphite","slug":"buffer-mapping-communication-failed","errorCode":null,"errorMessage":"Buffer mapping communication failed","messagePattern":"Buffer mapping communication failed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"node-graph/libraries/wgpu-executor/src/texture_conversion.rs","lineNumber":219,"sourceCode":"\t\tlet mut rows_meta = Vec::new();\n\n\t\tfor row in self {\n\t\t\tlet (element, attributes) = row.into_parts();\n\t\t\tconverters.push(RasterGpuToRasterCpuConverter::new(device, &mut encoder, element));\n\t\t\trows_meta.push(Item::from_parts((), attributes));\n\t\t}\n\n\t\tqueue.submit([encoder.finish()]);\n\n\t\tlet mut map_futures = Vec::new();\n\t\tfor converter in converters {\n\t\t\tmap_futures.push(converter.convert(device));\n\t\t}\n\n\t\tlet map_results = futures::future::try_join_all(map_futures)\n\t\t\t.await\n\t\t\t.map_err(|_| \"Failed to receive map result\")\n\t\t\t.expect(\"Buffer mapping communication failed\");\n\n\t\tmap_results\n\t\t\t.into_iter()\n\t\t\t.zip(rows_meta)\n\t\t\t.map(|(element, row)| {\n\t\t\t\tlet (_, attributes) = row.into_parts();\n\t\t\t\tItem::from_parts(element, attributes)\n\t\t\t})\n\t\t\t.collect()\n\t}\n}\n\n/// Converts single GPU raster to CPU by downloading texture data\nimpl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {\n\tasync fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster<CPU> {\n\t\tlet device = &executor.context().device;\n\t\tlet queue = &executor.context().queue;\n","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/node-graph/libraries/wgpu-executor/src/texture_conversion.rs#L201-L237","documentation":"When converting a list of GPU rasters to CPU (texture_conversion.rs:191), the executor submits one batched copy encoder and then awaits map futures for every readback buffer. Each converter polls the device (wgt::PollType::wait_indefinitely) and resolves a channel with the map_async result; try_join_all fails if any single buffer map is rejected (device lost, buffer invalid, mapping error), and .expect(\"Buffer mapping communication failed\") aborts the whole batch conversion. The map error originates in wgpu's map_async callback being invoked with an error.","triggerScenarios":"Batch GPU-to-CPU conversion where any converter.convert() returns Err: the wgpu device was lost mid-readback (driver reset, adapter removed), a readback buffer was invalidated or dropped before its map completed, a map_async was rejected due to invalid buffer state, or the per-converter receiver.await failed while delivering the map result.","commonSituations":"GPU driver crash or adapter hot-unplug during export of many images; very large batch downloads racing with shutdown/teardown so buffers are freed while maps are pending; wgpu version upgrades changing map/poll semantics; CI or VM environments with flaky software adapters (lavapipe/LLVMpipe).","solutions":["Check for device loss first: register a device-lost / uncaptured-error handler on the wgpu device; if lost, re-create the WgpuExecutor and retry the conversion once","Verify readback buffers are created with MAP_READ | COPY_DST usage and outlive their pending maps (do not drop converters or their buffers early)","Propagate the failure instead of .expect: return a conversion error from the trait method so callers can retry or degrade per-image","If it only fails for very large batches, split the list into smaller batches to limit blast radius and identify the offending raster"],"exampleFix":"// before\nlet map_results = futures::future::try_join_all(map_futures)\n    .await\n    .map_err(|_| \"Failed to receive map result\")\n    .expect(\"Buffer mapping communication failed\");\n\n// after\nlet map_results = futures::future::try_join_all(map_futures)\n    .await\n    .map_err(|_| RasterConversionError::BufferMapFailed)?; // caller re-creates the executor on device loss, then retries","handlingStrategy":"retry","validationCode":"// Register once at executor creation so device loss is observed instead of surfacing as failed maps:\ncontext.device.on_uncaptured_error(Box::new(|err| log::error!(\"wgpu error: {err:?}\")));\n// Before a large batch readback, drain pending device work so failures surface early:\nlet _ = context.device.poll(wgpu::wgt::PollType::wait_indefinitely());","typeGuard":null,"tryCatchPattern":"use futures::FutureExt;\nlet result = std::panic::AssertUnwindSafe(convert_list(gpu_rasters, executor))\n    .catch_unwind()\n    .await;\nmatch result {\n    Ok(Ok(rasters)) => { /* success */ }\n    Ok(Err(_)) | Err(_) => {\n        // likely device loss: re-create the WgpuExecutor, then retry the batch once (or degrade to CPU processing)\n    }\n}","preventionTips":["Treat any wgpu device-lost or uncaptured-error log line as a hard stop: re-create the executor before issuing more readbacks","Keep readback buffers and converters alive until their map futures resolve; never free GPU resources with maps pending","Keep the wgpu device polled while buffer maps are awaited","Wrap batch GPU-to-CPU conversions so one failed map retries or degrades instead of aborting the whole export"],"tags":["rust","wgpu","gpu","buffer-mapping","async","panic"],"backgroundTag":"wgpu-buffer-map-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}