Skip to content

Crash on MacOS #434

Open
Open

Description

@InvisibleFunction

Dragging a tiff to the sidebar results in a crash:

Fatal Python error: Segmentation fault

Thread 0x000000016e867000 (most recent call first):
  File "threading.py", line 359 in wait
  File "threading.py", line 660 in wait
  File "wgpu/backends/wgpu_native/_poller.py", line 101 in run
  File "threading.py", line 1044 in _bootstrap_inner
  File "threading.py", line 1015 in _bootstrap

Current thread 0x00000001f9d7de80 (most recent call first):
  File "negpy/desktop/session.py", line 227 in refresh
  File "negpy/desktop/controller.py", line 329 in _on_thumbnails_finished
  File "negpy/desktop/main.py", line 126 in main
  File "desktop.py", line 29 in <module>

Extension modules: PyQt6.QtCore, PyQt6.QtGui, PyQt6.QtWidgets, numpy._core._multiarray_umath, numpy.linalg._umath_linalg, PIL._imaging, PIL._imagingcms, rawpy._rawpy, numba.core.typeconv._typeconv, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._pcg64, numpy.random._generator, numpy.random._mt19937, numpy.random._philox, numpy.random._sfc64, numpy.random.mtrand, numba._helperlib, numba._dynfunc, numba._dispatcher, numba.core.runtime._nrt_python, numba.np.ufunc._internal, numba.experimental.jitclass._box, markupsafe._speedups, _cffi_backend, PyQt6.QtOpenGL, PyQt6.QtOpenGLWidgets, PIL._imagingmath (total: 29)
Segmentation fault: 11     /Applications/NegPy.app/Contents/MacOS/NegPy

This is on MacOS 26.5 (25F71) and NegPy v0.35.0

Activity

  1. marcinz606 commented on Jul 10, 2026

    @marcinz606
    Owner

    I think it might be macos-specific regression from multithreading changes in 0.35.0

  2. marcinz606 commented on Jul 10, 2026

    @marcinz606
    Owner

    can you checkout fix/434-gpu-display-texture-uaf branch to verify if it fixes it? I don't currently have macbook on hand, only linux and there there is no issue with threading

  3. seanharding commented on Jul 10, 2026

    @seanharding
    Contributor

    I can't reproduce the bug in the 0.35.0 release build, so I'm not sure I can effectively test the fix. Am I not understanding the repro steps? I launched NegPy, then drug a tiff file (also tried a nef) from the finder into the left sidebar of NegPy. On my machine running macOS 26.5.1, those files loaded without issue.

    Happy to try testing the fix if someone can confirm how to reproduce the issue in the first place…

  4. InvisibleFunction commented on Jul 11, 2026

    @InvisibleFunction
    Author

    I poked around a bit more and I got a tiff to open, but it crashed with a similar segfault when I started dragging around the Analysis Buffer slider.

    I downgraded to 0.34 and things seemed to work much better. I edited an image and then clicked the export button and got an identical crash...

    
    Thread 0x000000016ede7000 (most recent call first):
      File "threading.py", line 359 in wait
      File "threading.py", line 660 in wait
      File "wgpu/backends/wgpu_native/_poller.py", line 101 in run
      File "threading.py", line 1044 in _bootstrap_inner
      File "threading.py", line 1015 in _bootstrap
    
    Current thread 0x00000001f9d7de80 (most recent call first):
      File "negpy/desktop/session.py", line 225 in refresh
      File "negpy/desktop/controller.py", line 325 in _on_thumbnails_finished
      File "negpy/desktop/main.py", line 115 in main
      File "desktop.py", line 29 in <module>
    
    Extension modules: PyQt6.QtCore, PyQt6.QtGui, PyQt6.QtWidgets, numpy._core._multiarray_umath, numpy.linalg._umath_linalg, PIL._imaging, PIL._imagingcms, rawpy._rawpy, numba.core.typeconv._typeconv, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._pcg64, numpy.random._generator, numpy.random._mt19937, numpy.random._philox, numpy.random._sfc64, numpy.random.mtrand, numba._helperlib, numba._dynfunc, numba._dispatcher, numba.core.runtime._nrt_python, numba.np.ufunc._internal, numba.experimental.jitclass._box, markupsafe._speedups, _cffi_backend, PyQt6.QtOpenGL, PyQt6.QtOpenGLWidgets, PIL._imagingmath (total: 29)
    Segmentation fault: 11     /Applications/NegPy.app/Contents/MacOS/NegPy
    
  5. InvisibleFunction commented on Jul 11, 2026

    @InvisibleFunction
    Author

    I tried running from your branch, let me know if this is correct:

    (.venv) me@compy:~/git/NegPy$ uv sync --group dev
    Resolved 43 packages in 6ms
    Installed 39 packages in 281ms
     + altgraph==0.17.5
     + cffi==2.0.0
     + coverage==7.14.0
     + imagecodecs==2026.6.6
     + imageio==2.37.3
     + iniconfig==2.3.0
     + jinja2==3.1.6
     + llvmlite==0.47.0
     + macholib==1.16.4
     + markupsafe==3.0.3
     + negpy==0.35.0 (from file:///Users/me/git/NegPy)
     + numba==0.65.1
     + numpy==2.4.4
     + opencv-python-headless==4.13.0.92
     + packaging==26.2
     + piexif==1.1.3
     + pillow==12.2.0
     + pluggy==1.6.0
     + pycparser==3.0
     + pygments==2.20.0
     + pyinstaller==6.20.0
     + pyinstaller-hooks-contrib==2026.5
     + pyqt6==6.11.0
     + pyqt6-charts==6.11.0
     + pyqt6-charts-qt6==6.11.1
     + pyqt6-qt6==6.11.1
     + pyqt6-sip==13.11.1
     + pytest==9.0.2
     + pytest-cov==7.1.0
     + qtawesome==1.4.2
     + qtpy==2.4.3
     + rawpy==0.27.0
     + rendercanvas==2.6.3
     + rubicon-objc==0.5.4
     + ruff==0.14.10
     + setuptools==82.0.1
     + tifffile==2026.6.1
     + ty==0.0.37
     + wgpu==0.31.1
    (.venv) me@compy:~/git/NegPy$ uv run python desktop.py
    [2026-07-10 21:58:42] INFO     negpy.negpy.kernel.system.parallel: CPU parallel kernels: disabled (override=None, platform=darwin)
    [2026-07-10 21:58:43] INFO     negpy.negpy.infrastructure.storage.local_asset_store: LocalAssetStore initialized at /Users/me/Documents/NegPy/cache
    Forcing backend: Metal (5)
    [2026-07-10 21:58:44] INFO     negpy.negpy.infrastructure.gpu.device: WebGPU Backend: Apple M2 Max (IntegratedGPU) via Metal
    [2026-07-10 21:58:44] INFO     negpy.negpy.services.rendering.image_processor: ImageProcessor: Acceleration backend ready
    [2026-07-10 21:58:44] INFO     negpy.negpy.services.rendering.image_processor: ImageProcessor: Acceleration backend ready
    Populating font family aliases took 64 ms. Replace uses of missing font family "Inter" with one that exists to avoid this cost.
    QAccessibleList::child: Invalid index at: 0 0
    QAccessibleList::child: Invalid index at: 0 0
    QAccessibleList::child: Invalid index at: 0 0
    QAccessibleList::child: Invalid index at: 0 0
    [2026-07-10 21:58:57] INFO     negpy.negpy.services.rendering.gpu_engine: GPUEngine: VRAM resources released
    [2026-07-10 21:58:58] INFO     negpy.negpy.services.rendering.preview_manager: load-timing decode.postprocess 285ms (fast=False) /Users/me/Desktop/Image 1 (4).tif
    [2026-07-10 21:58:58] INFO     negpy.negpy.services.rendering.preview_manager: load-timing decode.resize 41ms
    [2026-07-10 21:58:58] INFO     negpy.negpy.services.rendering.preview_manager: load-timing decode.total 390ms (demosaic+orient+resize)
    [2026-07-10 21:58:58] INFO     negpy.negpy.services.rendering.preview_manager: load-timing load_splash_and_linear 855ms (decode 394ms + open)
    [2026-07-10 21:58:58] INFO     negpy.negpy.desktop.workers.render: load-timing preview_worker_total 855ms (load->buffer) /Users/me/Desktop/Image 1 (4).tif
    [2026-07-10 21:58:58] INFO     negpy.negpy.desktop.controller: load-timing preview_e2e 888ms (load request -> decoded buffer) /Users/me/Desktop/Image 1 (4).tif
    [2026-07-10 21:58:59] INFO     negpy.negpy.services.rendering.gpu_engine: load-timing gpu_init 889ms (compiled 14 shaders/pipelines)
    [2026-07-10 21:58:59] INFO     negpy.negpy.services.rendering.gpu_engine: load-timing analysis 181ms (bounds=False refs=True anchor=True textural=True)
    [2026-07-10 21:58:59] INFO     negpy.negpy.desktop.controller: load-timing first_render 1573ms (buffer -> painted) /Users/me/Desktop/Image 1 (4).tif
    Fatal Python error: Segmentation fault
    
    Thread 0x0000000170953000 (most recent call first):
      File "/opt/local/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 359 in wait
      File "/opt/local/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 660 in wait
      File "/Users/me/git/NegPy/.venv/lib/python3.13/site-packages/wgpu/backends/wgpu_native/_poller.py", line 101 in run
      File "/opt/local/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 1044 in _bootstrap_inner
      File "/opt/local/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/threading.py", line 1015 in _bootstrap
    
    Current thread 0x00000001f9d7de80 (most recent call first):
      File "/Users/me/git/NegPy/negpy/desktop/session.py", line 227 in refresh
      File "/Users/me/git/NegPy/negpy/desktop/controller.py", line 329 in _on_thumbnails_finished
      File "/Users/me/git/NegPy/negpy/desktop/main.py", line 126 in main
      File "/Users/me/git/NegPy/desktop.py", line 29 in <module>
    
    Extension modules: PyQt6.QtCore, PyQt6.QtGui, PyQt6.QtWidgets, numpy._core._multiarray_umath, numpy.linalg._umath_linalg, PIL._imaging, PIL._imagingcms, rawpy._rawpy, numba.core.typeconv._typeconv, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._pcg64, numpy.random._generator, numpy.random._mt19937, numpy.random._philox, numpy.random._sfc64, numpy.random.mtrand, numba._helperlib, numba._dynfunc, numba._dispatcher, numba.core.runtime._nrt_python, numba.np.ufunc._internal, numba.experimental.jitclass._box, markupsafe._speedups, _cffi_backend, PyQt6.QtOpenGL, PyQt6.QtOpenGLWidgets (total: 28)
    (.venv) me@compy:~/git/NegPy$ git branch
    * fix/434-gpu-display-texture-uaf
      main
    (.venv) me@compy:~/git/NegPy$
    
  6. linkmodo commented on Jul 11, 2026

    @linkmodo
    Contributor

    Looks like a crash on GPU rendering. Turn off GPU rendering (the lightning bolt icon at bottom toolbar) and switch to CPU rendering does the crash still happen when doing the same action?

    Recommended action from Gemini based on console error:

    • Your script crashed due to a Segmentation fault in the WebGPU polling thread, caused by a conflict between Python 3.13, your wgpu-native library, and the Apple M2 Max GPU driver.
    • Update your backend dependencies. Install the latest version of the wgpu PyPI Package to ensure compatibility with Apple silicon.
  7. InvisibleFunction commented on Jul 11, 2026

    @InvisibleFunction
    Author

    I unclicked the lightning bolt icon and I still got the same crash. I'll try to update to the latest mac point release.

    (.venv) me@compy:~/git/NegPy$ uv pip show wgpu
    Name: wgpu
    Version: 0.31.1
    Location: /Users/me/git/NegPy/.venv/lib/python3.13/site-packages
    Requires: cffi, rendercanvas, rubicon-objc
    Required-by: negpy
    

    Looks like the latest.

  8. marcinz606 commented on Jul 11, 2026

    @marcinz606
    Owner

    If it also happens on 0.34.0 then 0.35.0 multithreading changes are not the culprit, interesting

  9. InvisibleFunction commented on Jul 11, 2026

    @InvisibleFunction
    Author

    Here's what claude thinks:

    RenderWorker.cleanup() (negpy/desktop/workers/render.py:116-118) unconditionally calls ImageProcessor.cleanup(), which unconditionally calls self.engine_gpu.cleanup(collect=collect) whenever self.engine_gpu exists (negpy/services/rendering/image_processor.py:779-780) — it never checks prefer_gpu / the user's GPU toggle. That toggle only gates which backend processes pixels for a given call; it doesn't tear down or bypass GPUEngine, the underlying wgpu.GPUDevice singleton (negpy/infrastructure/gpu/device.py), or wgpu-native's own background poller thread — all of which stay alive for the app's lifetime once initialized, regardless of the toggle.
    That poller thread shows up in every crash trace collected so far, always blocked in threading.Event.wait() inside wgpu/backends/wgpu_native/_poller.py:101. It's a native background thread that polls the device for GPU work completion.
    So on every file load/switch, GPUEngine.cleanup() runs tex.destroy() on the pooled wgpu/Metal textures from a worker thread — while wgpu-native's own poller thread is concurrently alive and touching the same device from a different thread, with nothing synchronizing the two. That's consistent with genuine cross-thread Metal/wgpu-native resource-destruction corruption, which would explain:

    • why the crash trace stays identical across totally different user actions (load, reselect, export) — they all trigger a file/task switch, which all hit this same unsynchronized cleanup path;
    • why downgrading wgpu (0.35 → 0.34) didn't fix it, just shifted when enough concurrent activity piled up to trigger the corruption — consistent with a timing-sensitive race rather than a version-specific regression;
    • why it still happens with GPU rendering disabled — the toggle never stops the underlying device/cleanup machinery from running.
  10. Vipitis commented on Jul 12, 2026

    @Vipitis

    wgpu-py contributor here,
    For multithreading I am not sure what wgpu guarantees, I am sorta surprised it works at all. Regarding the whole instance cleanup - that's definitely not intended so it will likely break in the future. If you file an issue and explain the requirement perhaps I find some time to look into it, as I also have a usecase for trying shaders in parallel and letting them crash gracefully.

  11. InvisibleFunction commented on Jul 12, 2026

    @InvisibleFunction
    Author

    I just tested with the same tiffs on windows and it worked fine if that’s an interesting datapoint.

  12. marcinz606 commented on Jul 12, 2026

    @marcinz606
    Owner

    Can you upload that tiff here? I cannot replicate the crash on linux or macos using my files

  13. InvisibleFunction commented on Jul 12, 2026

    @InvisibleFunction
    Author

    https://have-some-files.s3.amazonaws.com/lillies.tif

    I tried it on a Windows machine and it seemed to work fine.

  14. thetalkingdrum commented on Aug 15, 2026

    @thetalkingdrum
    Contributor

    Possible new data point for this: I hit what looks like the same underlying race, but escalating further than the segfault reported above.

    Environment: NegPy 0.51.0 (packaged .app), macOS 15.7.9 (24G830), MacBook Pro 18,1 / M1 Pro / 16 GB unified memory, Metal backend.

    On a normal render session (switching between a couple of loaded TIFFs), NegPy's own logging went completely silent mid-session — no exception, no traceback, nothing — while macOS's runningboardd kept reporting the process as running-active for another ~15 minutes. No .crash/.ips report was ever generated for NegPy. The machine then required a hardware-forced restart (power button held) to recover — confirmed by a macOS kernel panic log with panic string btn_rst, timestamped seconds before the app relaunched. System-wide unified logging (kernel, WiFi, Bluetooth, daemons) kept running normally the whole time, which rules out a full kernel lockup and points at a wedged GPU/display command queue specifically — WindowServer shares the GPU with app submissions, so one app jamming that queue can freeze the whole screen while background kernel work continues underneath.

    Interesting overlap: I'd downloaded and loaded lillies.tif (the repro file from this thread) on this same machine three weeks earlier, and loaded it again right after recovering from this freeze — so this is the same repro case, on a machine that (per the fix branch's own module docstring) has the not-yet-verified-safe cpu_parallel setting available, though I've since confirmed that setting wasn't the cause here (a Numba abort kills a process in under a second and wouldn't need a hardware reset to recover from).

    Full evidence/timeline write-up (log excerpts, panic log, ruled-out alternate theory) is here if useful: thetalkingdrum#4

    Given Vipitis's point above that wgpu-native doesn't guarantee safety for concurrent device access from the cleanup path and the poller thread — on unlucky timing that same unsynchronized destroy could plausibly corrupt enough Metal/driver state to wedge the whole GPU queue rather than just segfault the Python process. Flagging in case it's useful signal for prioritizing/verifying fix/434-gpu-display-texture-uaf.

  15. thetalkingdrum commented on Aug 16, 2026

    @thetalkingdrum
    Contributor

    Full write-up behind my data point above (moving it here instead of keeping it on a fork issue, since that's not visible from this thread):

    Summary

    On 2026-08-15 around 23:18–23:35 local time, NegPy (packaged .app, v0.51.0) was open and being used to compare two loaded negatives. The whole machine — not just NegPy — stopped responding to any input and had to be recovered with a forced hardware restart (power button held). Confirmed by a macOS kernel panic log with panic string btn_rst (the panic macOS logs specifically for a user-forced restart), timestamped 9 seconds before the next NegPy session booted.

    Environment

    • NegPy 0.51.0, packaged /Applications/NegPy.app (ad-hoc signed), not a dev run
    • macOS 15.7.9 (24G830)
    • MacBook Pro 18,1 (M1 Pro, 16 GB unified memory)
    • GPU backend: Apple M1 Pro (IntegratedGPU) via Metal (rendering.backend = "metal" in override.toml)
    • override.toml had [performance] cpu_parallel = true — see "Ruled out" below

    Timeline (from ~/Documents/NegPy/negpy.log and macOS's unified log, cross-referenced by PID)

    Time Event
    23:18:23 New NegPy instance launches (app<...>:PID 8155). CPU parallel kernels: enabled (override=True, platform=darwin)
    23:18:32–23:18:36 Loads/renders two files fine: c2025-04-13-0002.tif and Kodak Gold 200_Frame028_R_linear.tiff (last successful first_render logged at 23:18:36)
    23:18:36 Last line NegPy's Python logger ever writes for this session. No exception, no traceback, nothing.
    23:18:36 – 23:33:55 negpy.log: total silence. macOS's runningboardd, however, keeps reporting PID 8155 as running-active the whole time — the process is alive at the OS level, just producing no application-level activity. Last such state update: 23:33:55.610.
    23:25:00 – 23:34:59 System-wide unified log stays busy the entire time — thousands of lines/minute from kernel, WiFi, Bluetooth, mDNSResponder, etc. (checked explicitly to rule out a full kernel lockup — see "Ruled out" below).
    23:35:14 Kernel panic, panic(cpu 1 caller ...): btn_rst — logged to /Library/Logs/DiagnosticReports/forceReset-full-2026-08-15-233514.0002.diag. This is macOS's panic string for a user-forced hardware restart (power button held), not an automatic fault/watchdog panic.
    23:35:18 New NegPy launch request (likely auto-relaunch via macOS's "reopen windows on login" / PersistentAppsSupport, seen in loginwindow logs)
    23:35:23 Fresh NegPy session boots, negpy.log resumes
    23:35:57 WARNING desktop.main: CPU parallel kernels disabled after an unclean shutdown — the app's own self-check (main.py:_offer_to_disable_cpu_parallel) correctly detected the prior session never called clean_shutdown, and I accepted the dialog offering to turn cpu_parallel off

    No .ips/.crash report for NegPy itself exists anywhere in ~/Library/Logs/DiagnosticReports or /Library/Logs/DiagnosticReports for this window — only the system-level forceReset-full / ResetCounter pair from the forced restart.

    What was loaded

    The file most likely in view when things went silent: Kodak Gold 200_Frame028_R_linear.tiff, 216 MB, 7968×5320, 16-bit RGB, Adobe-deflate compressed, unusually small strips (RowsPerStrip=5, 1064 strips), source SONY ILCE-7RM2. It had loaded successfully many times before and after this incident (both in this same session and the recovery session right after), so the file by itself isn't reliably reproducing a hang — something about the sequence of actions around 23:18:36 is implicated, not just "open this file."

    Ruled out: Numba CPU-parallel abort

    negpy/kernel/system/parallel.py documents a known failure mode: Numba's workqueue threading layer hard-aborts the process with no Python exception and nothing written to the log if two threads enter a parallel=True kernel concurrently. override.toml had cpu_parallel = true forced on macOS, despite the code's own comment that this is unverified there ("Defaults to true except on macOS, where it defaults to false while crash reports are investigated"). The silent-log symptom initially looked like a strong match.

    Ruled out because:

    • That abort kills a single process almost immediately — it would not explain 15 minutes of the process still showing as running-active to runningboardd, and would not require a hardware forced restart to recover from.
    • The system-wide unified log (kernel, WiFi, Bluetooth, daemons) kept logging normally through the entire silent window, which is inconsistent with a process-fatal abort but consistent with "one process's screen output froze while the rest of the OS kept running."

    I've since turned cpu_parallel off in override.toml (real, separate, documented risk per the module's own comments), but it's very unlikely to be the cause of this incident.

    Leading theory: GPU/display-pipeline wedge (unconfirmed)

    The pattern — one process's window stops updating/responding while background kernel and daemon activity continues normally, ending in a state only a forced hardware restart recovers from, with no automatic GPU-watchdog panic ever firing on its own — matches a wedged Metal/AGX GPU command queue. macOS's WindowServer shares the GPU with every app's Metal/WebGPU submissions; if one app's GPU work jams that shared queue, the whole screen can lock solid (no cursor movement, Force Quit unreachable) while unrelated kernel subsystems (which don't touch the GPU) keep running fine underneath — exactly what the per-minute unified-log activity shows.

    NegPy's GPUEngine (negpy/services/rendering/gpu_engine.py, WGSL via wgpu) was the last thing doing GPU work before the silence. Candidate contributing factors, none confirmed:

    • The image is ~127 megapixels; the pipeline runs a dozen-plus GPU compute stages per render, each potentially allocating a full-resolution intermediate buffer.
    • The machine has 16 GB unified memory, shared between CPU and GPU — a large enough working set across pipeline stages could hit real memory pressure, which is one known trigger class for Apple Silicon GPU driver wedges.
    • The file's unusual strip layout (RowsPerStrip=5, 1064 strips) is not obviously GPU-relevant (that's a CPU-side TIFF decode detail), but is flagged here in case it correlates with something on the decode → upload path.

    No automatic GPU-watchdog panic preceded the forced one, so macOS's own driver-level recovery never engaged — meaning either the wedge was too deep for that mechanism, or the freeze wasn't purely a self-contained GPU-driver bug at all. I don't have a way to distinguish those from the outside.

    Given the cross-thread cleanup/poller race discussed above in this thread (confirmed by Vipitis as unsupported by wgpu-native), that same unsynchronized tex.destroy() racing the poller thread is my best guess for what's actually behind this — just landing as driver-state corruption severe enough to wedge the GPU queue instead of a clean segfault.

    Open questions

    1. Can this be reproduced deliberately with a large (>100 MP) source and heavy GPU-stage usage (e.g. defect repair / textural analysis, which the log shows was active — textural=True — on this file shortly before other loads that session)?
    2. Does GPUEngine's per-stage buffer lifecycle release intermediate textures between stages, or could VRAM/unified-memory usage accumulate across repeated loads of a huge file within one session?
    3. Is there any bound today on max_texture_size for a file this large, or does "auto" let a 7968×5320 image go through uncapped?
    4. Worth adding a client-side watchdog (e.g. a heartbeat timer that detects the GPU submission queue not returning within N seconds) so a wedge surfaces as a recoverable error dialog instead of a silent full-system freeze?

    Evidence sources

    • ~/Documents/NegPy/negpy.log (session logging, self-diagnostic messages)
    • ~/Documents/NegPy/override.toml (the cpu_parallel = true setting)
    • macOS unified log (log show), filtered by PID and by system-wide per-minute activity
    • /Library/Logs/DiagnosticReports/forceReset-full-2026-08-15-233514.0002.diag and the paired ResetCounter-2026-08-15-233521.diag
    • tifffile inspection of the source TIFF
  16. thetalkingdrum commented on Aug 16, 2026

    @thetalkingdrum
    Contributor

    Follow-up on my report above: I dug into the raw kernel panic snapshot and it captured NegPy's full process/thread state at the exact moment of the panic. Worth sharing since it sharpens the theory:

    All 31 of NegPy's threads were parked in TH_WAIT — none running, none runnable. That's a deadlock signature, not a busy spin loop or a stuck-but-progressing compute shader. One thread (tid 459933) had accumulated far more CPU time (23.1s user + 5.7s system) than any other before going idle, consistent with it being the render/GPU-submission thread that was doing the actual work right before everything stopped. Every thread's lastRunTime clusters in the same ~15-17s-before-panic window — the whole process went quiet at essentially the same moment, not a slow cascade.

    That fits the cross-thread GPUEngine.cleanup() / wgpu-native poller race discussed above better than a pure driver-corruption theory: if the destroy path and the poller end up mutually blocked on some internal wgpu-native/Metal lock, every thread touching that device (all of NegPy's QThread workers) would end up parked exactly like this.

    Redacted raw evidence, if useful for cross-checking: https://gist.github.com/thetalkingdrum/63400a69a93bec3e3d7989669c31fb8c (log excerpt, the config at crash time, and a curated excerpt of the panic report — full raw .diag available on request, left out here since it's 2.6MB of mostly boilerplate kext-version listing).

  17. thetalkingdrum commented on Aug 16, 2026

    @thetalkingdrum
    Contributor

    Small caveat on my reports above, before it misleads anyone: I think I jumped to the wgpu race too fast.

    Checked macOS's disk-space telemetry and my boot volume was down to ~300MB free at 11:26 and ~3.8GB at 23:33 — both right before my two forced restarts that day (turns out there were two, same btn_rst panic, not just the one I originally reported — found the second one, at 11:31, while digging further). A near-full disk can freeze a whole Mac solid on its own, no GPU bug needed — swap can't grow, background daemons can't write temp files, everything backs up.

    That also undercuts my "all 31 threads in TH_WAIT" finding — a disk/swap stall looks exactly the same (everything blocked, nothing running), so it doesn't actually point at the wgpu race specifically. My bad for reading it that way.

    Not saying the race isn't real — Vipitis confirmed it independently, and it's presumably still what's causing the clean segfaults others are hitting. Just that my particular freeze might be a "my disk was basically full" problem rather than this one. Clearing space and keeping an eye on it before I assume otherwise.

  18. InvisibleFunction commented on Oct 12, 2026

    @InvisibleFunction
    Author

    Just as a note I tried the 0.63.0 release today and was able to do much more before it eventually crashed with the same shaped output:

    ...
    QAccessibleTree::indexFromLogical: invalid index:  0 1  for  QTreeWidget(0x8c47a5180)
    Requested invalid tree cell: 0 1
    QAccessibleTree::indexFromLogical: invalid index:  0 0  for  QTreeWidget(0x8c47a5180)
    Requested invalid tree cell: 0 0
    QAccessibleTree::indexFromLogical: invalid index:  0 1  for  QTreeWidget(0x8c47a5180)
    Requested invalid tree cell: 0 1
    Fatal Python error: Segmentation fault
    
    Thread 0x000000016e3fb000 (most recent call first):
      File "threading.py", line 359 in wait
      File "threading.py", line 660 in wait
      File "wgpu/backends/wgpu_native/_poller.py", line 101 in run
      File "threading.py", line 1044 in _bootstrap_inner
      File "threading.py", line 1015 in _bootstrap
    
    Current thread 0x00000001eee7e1c0 (most recent call first):
      File "negpy/desktop/session.py", line 457 in _apply_reindex
      File "negpy/desktop/session.py", line 636 in refresh
      File "negpy/desktop/controller.py", line 1314 in _on_rendered_thumbnail
      File "negpy/desktop/main.py", line 306 in main
      File "desktop.py", line 34 in <module>
    
    Extension modules: PyQt6.QtCore, PyQt6.QtGui, PyQt6.QtWidgets, numpy._core._multiarray_umath, numpy.linalg._umath_linalg, numba.core.typeconv._typeconv, numpy.random._common, numpy.random.bit_generator, numpy.random._bounded_integers, numpy.random._pcg64, numpy.random._generator, numpy.random._mt19937, numpy.random._philox, numpy.random._sfc64, numpy.random.mtrand, numba._helperlib, numba._dynfunc, numba._dispatcher, numba.core.runtime._nrt_python, numba.np.ufunc._internal, numba.experimental.jitclass._box, PIL._imaging, PIL._imagingcms, rawpy._rawpy, markupsafe._speedups, PIL._imagingft, _cffi_backend, PyQt6.QtOpenGL, PyQt6.QtOpenGLWidgets, PIL._imagingmath (total: 30)
    Segmentation fault: 11     /Applications/NegPy.app/Contents/MacOS/NegPy
    

    I was able to edit several photos and even export some.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions