From f6fe61343c69fcb571412aa0f7ea39487f0a9597 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 13:45:37 -0400 Subject: [PATCH 1/6] chore(libjpeg-turbo): update extern/libjpeg-turbo submodule to upstream 3.2.0 Advances both the 8-bit and 12-bit packages' shared submodule from dc4a93f (2.1.4-era, Dec 2022) to upstream 3.2.0 (2026-06-30). No custom fork patches (clean version advance). Fork PR: cornerstonejs/libjpeg-turbo#1. Major-version jump (2.x -> 3.x): CI is the first build of 3.2.0 against our 8-bit and 12-bit glue; API drift (incl. 3.x's unified precision handling vs the old WITH_12BIT flag) is expected and will be iterated. --- packages/libjpeg-turbo-12bit/extern/libjpeg-turbo | 2 +- packages/libjpeg-turbo-8bit/extern/libjpeg-turbo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo b/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo index dc4a93fa..c85e6b90 160000 --- a/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo +++ b/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo @@ -1 +1 @@ -Subproject commit dc4a93fab38b42d29b89a533409e012570180e28 +Subproject commit c85e6b905bf237038faa936dab160ebfc5da0344 diff --git a/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo b/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo index dc4a93fa..c85e6b90 160000 --- a/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo +++ b/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo @@ -1 +1 @@ -Subproject commit dc4a93fab38b42d29b89a533409e012570180e28 +Subproject commit c85e6b905bf237038faa936dab160ebfc5da0344 From 5e15392a4520f0e5f0adcfb9403c9d7564082fc0 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 14:16:39 -0400 Subject: [PATCH 2/6] build(libjpeg-turbo-8bit): build libjpeg-turbo 3.x as a separate project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libjpeg-turbo 3.x forbids add_subdirectory() integration, so build it standalone (its own emscripten cmake) and link the produced libturbojpeg.a as an IMPORTED target. Handles 3.x layout changes: headers moved under src/, disable the new SPNG/ZLIB dep (WITH_SPNG=0). No glue changes — the legacy TurboJPEG API our wrapper uses (tjInitDecompress/tjDecompress2/...) is still present in 3.2.0. First blind cut; iterating on CI. 12-bit rework to follow. --- packages/libjpeg-turbo-8bit/CMakeLists.txt | 12 ++++---- packages/libjpeg-turbo-8bit/build.sh | 29 ++++++++++++------- .../libjpeg-turbo-8bit/src/CMakeLists.txt | 13 ++++++++- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/libjpeg-turbo-8bit/CMakeLists.txt b/packages/libjpeg-turbo-8bit/CMakeLists.txt index 00807fdb..431e9238 100644 --- a/packages/libjpeg-turbo-8bit/CMakeLists.txt +++ b/packages/libjpeg-turbo-8bit/CMakeLists.txt @@ -31,11 +31,13 @@ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/libjpeg-turbo/CMakeLists.txt") message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") endif() -option(ENABLE_SHARED "" OFF) -option(ENABLE_STATIC "" ON) - -# add the external library -add_subdirectory(extern/libjpeg-turbo EXCLUDE_FROM_ALL) +# NOTE: libjpeg-turbo 3.x refuses add_subdirectory() integration (it asserts it +# is the top-level project and errors out). It is now built as a separate +# project by build.sh, which passes its build dir in LIBJPEG_TURBO_BUILD_DIR; +# src/CMakeLists.txt links the produced libturbojpeg.a as an IMPORTED target. +if(EMSCRIPTEN AND NOT DEFINED LIBJPEG_TURBO_BUILD_DIR) + message(FATAL_ERROR "LIBJPEG_TURBO_BUILD_DIR not set — run build.sh, which builds libjpeg-turbo first and passes its build dir.") +endif() # add the js wrapper if(EMSCRIPTEN) diff --git a/packages/libjpeg-turbo-8bit/build.sh b/packages/libjpeg-turbo-8bit/build.sh index 8741403d..489394be 100644 --- a/packages/libjpeg-turbo-8bit/build.sh +++ b/packages/libjpeg-turbo-8bit/build.sh @@ -1,18 +1,27 @@ #!/bin/sh # Disable exit on non 0 set +e -mkdir -p build -mkdir -p dist +rm -rf build build-libjpeg +mkdir -p build build-libjpeg dist -# DEBUG CONFIGURE -#(cd build && emcmake cmake -DCMAKE_BUILD_TYPE=Debug ..) && +# libjpeg-turbo 3.x forbids add_subdirectory() integration (its CMake asserts +# it is the top-level project). So we build it as a SEPARATE project first, +# then link the produced static lib (libturbojpeg.a) into our wasm wrapper. +# WITH_SIMD=0 keeps parity with the previous build; WITH_SPNG=0 avoids the new +# 3.x zlib/spng dependency (only used by the tj* tools, not our decode/encode). +echo "~~~ CONFIGURE libjpeg-turbo 3.x (standalone) ~~~" +(cd build-libjpeg && emcmake cmake -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SHARED=0 -DENABLE_STATIC=1 \ + -DWITH_SIMD=0 -DWITH_SPNG=0 -DWITH_TURBOJPEG=1 \ + ../extern/libjpeg-turbo) +echo "~~~ MAKE libjpeg-turbo ~~~" +(cd build-libjpeg && emmake make VERBOSE=1 -j 16 turbojpeg-static jpeg-static) -echo "~~~ CONFIGURE ~~~" -# Only include decoding to make it smaller -# see https://github.com/libjpeg-turbo/libjpeg-turbo/issues/431 -(cd build && emcmake cmake -G"Unix Makefiles" ..) -#(cd build && emcmake cmake -G"Unix Makefiles"..) -echo "~~~ MAKE ~~~" +echo "~~~ CONFIGURE wrapper ~~~" +LIBJPEG_TURBO_BUILD_DIR="$(cd build-libjpeg && pwd)" +(cd build && emcmake cmake -G"Unix Makefiles" -DLIBJPEG_TURBO_BUILD_DIR="$LIBJPEG_TURBO_BUILD_DIR" ..) +echo "~~~ MAKE wrapper ~~~" (cd build && emmake make VERBOSE=1 -j 16) echo "~~~ COPY ~~~ " cp ./build/src/libjpegturbowasm.js ./dist diff --git a/packages/libjpeg-turbo-8bit/src/CMakeLists.txt b/packages/libjpeg-turbo-8bit/src/CMakeLists.txt index 433ff18e..e6574954 100644 --- a/packages/libjpeg-turbo-8bit/src/CMakeLists.txt +++ b/packages/libjpeg-turbo-8bit/src/CMakeLists.txt @@ -1,6 +1,17 @@ - include_directories("../extern/libjpeg-turbo" "../build/extern/libjpeg-turbo") + # libjpeg-turbo 3.x moved its public headers under src/; jconfig.h is + # generated into the standalone build dir. turbojpeg-static is consumed as + # a pre-built IMPORTED library (built separately by build.sh — 3.x forbids + # add_subdirectory). + include_directories( + "../extern/libjpeg-turbo/src" + "${LIBJPEG_TURBO_BUILD_DIR}" + "${LIBJPEG_TURBO_BUILD_DIR}/src") + + add_library(turbojpeg-static STATIC IMPORTED) + set_target_properties(turbojpeg-static PROPERTIES + IMPORTED_LOCATION "${LIBJPEG_TURBO_BUILD_DIR}/libturbojpeg.a") add_executable(libjpegturbojs jslib.cpp) From 7c0c49af57b81e2d1ae4f253546af769da6b0782 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 14:42:16 -0400 Subject: [PATCH 3/6] build+fix(libjpeg-turbo-12bit): upgrade to libjpeg-turbo 3.x (multi-precision API) 3.x forbids add_subdirectory() and removed WITH_12BIT (one build is now multi-precision). Build libjpeg-turbo standalone and link libjpeg.a as an IMPORTED target (two-phase build.sh), and rewrite the decoder for 3.x: - decode grayscale 12-bit via jpeg12_read_scanlines + J12SAMPARRAY (the 3.x per-precision API) instead of jpeg_read_scanlines (the old WITH_12BIT model) - guard on num_components==1 and data_precision==12; overflow-checked sizing - correct single-component int16 output (no JCS_EXT_RGBA overflow) 3.x headers moved under src/. No dependency on #73 (left untouched); the decode-correctness fix here mirrors #73's grayscale logic but on the 3.x API. --- packages/libjpeg-turbo-12bit/CMakeLists.txt | 13 ++-- packages/libjpeg-turbo-12bit/build.sh | 27 +++++-- .../libjpeg-turbo-12bit/src/CMakeLists.txt | 12 ++- .../libjpeg-turbo-12bit/src/JPEGDecoder.hpp | 77 ++++++++++++------- 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/packages/libjpeg-turbo-12bit/CMakeLists.txt b/packages/libjpeg-turbo-12bit/CMakeLists.txt index 00807fdb..037815e2 100644 --- a/packages/libjpeg-turbo-12bit/CMakeLists.txt +++ b/packages/libjpeg-turbo-12bit/CMakeLists.txt @@ -31,11 +31,14 @@ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/libjpeg-turbo/CMakeLists.txt") message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") endif() -option(ENABLE_SHARED "" OFF) -option(ENABLE_STATIC "" ON) - -# add the external library -add_subdirectory(extern/libjpeg-turbo EXCLUDE_FROM_ALL) +# libjpeg-turbo 3.x refuses add_subdirectory() integration and dropped the +# WITH_12BIT build flag (a single build is now multi-precision: 8/12/16-bit). +# build.sh builds it as a separate project and passes its build dir in +# LIBJPEG_TURBO_BUILD_DIR; src/CMakeLists.txt links the produced libjpeg.a and +# the decoder uses the 3.x jpeg12_* API for 12-bit samples. +if(EMSCRIPTEN AND NOT DEFINED LIBJPEG_TURBO_BUILD_DIR) + message(FATAL_ERROR "LIBJPEG_TURBO_BUILD_DIR not set — run build.sh, which builds libjpeg-turbo first and passes its build dir.") +endif() # add the js wrapper if(EMSCRIPTEN) diff --git a/packages/libjpeg-turbo-12bit/build.sh b/packages/libjpeg-turbo-12bit/build.sh index 85932341..5043fc59 100644 --- a/packages/libjpeg-turbo-12bit/build.sh +++ b/packages/libjpeg-turbo-12bit/build.sh @@ -1,16 +1,27 @@ #!/bin/sh # Disable exit on non 0 set +e -rm -rf dist -mkdir -p build -mkdir -p dist +rm -rf build build-libjpeg dist +mkdir -p build build-libjpeg dist -# DEBUG CONFIGURE -#(cd build && emcmake cmake -DCMAKE_BUILD_TYPE=Debug ..) && +# libjpeg-turbo 3.x forbids add_subdirectory() and dropped WITH_12BIT — a single +# build is now multi-precision (8/12/16-bit), exposing jpeg12_* APIs. So build +# libjpeg-turbo as a SEPARATE project first (Release, WITH_SIMD=0 for parity, +# WITH_SPNG=0 to avoid the new zlib/spng dep), then link its libjpeg.a; the +# 12-bit decoder uses jpeg12_read_scanlines. +echo "~~~ CONFIGURE libjpeg-turbo 3.x (standalone, multi-precision) ~~~" +(cd build-libjpeg && emcmake cmake -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SHARED=0 -DENABLE_STATIC=1 \ + -DWITH_SIMD=0 -DWITH_SPNG=0 \ + ../extern/libjpeg-turbo) +echo "~~~ MAKE libjpeg-turbo ~~~" +(cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) -echo "~~~ CONFIGURE ~~~" -(cd build && emcmake cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DWITH_12BIT=1 ..) -echo "~~~ MAKE ~~~" +echo "~~~ CONFIGURE wrapper ~~~" +LIBJPEG_TURBO_BUILD_DIR="$(cd build-libjpeg && pwd)" +(cd build && emcmake cmake -G"Unix Makefiles" -DLIBJPEG_TURBO_BUILD_DIR="$LIBJPEG_TURBO_BUILD_DIR" ..) +echo "~~~ MAKE wrapper ~~~" (cd build && emmake make VERBOSE=1 -j 16) echo "~~~ COPY ~~~ " cp ./build/src/libjpegturbo12wasm.js ./dist diff --git a/packages/libjpeg-turbo-12bit/src/CMakeLists.txt b/packages/libjpeg-turbo-12bit/src/CMakeLists.txt index 57e688c2..092bd625 100644 --- a/packages/libjpeg-turbo-12bit/src/CMakeLists.txt +++ b/packages/libjpeg-turbo-12bit/src/CMakeLists.txt @@ -1,6 +1,16 @@ - include_directories("../extern/libjpeg-turbo" "../build/extern/libjpeg-turbo") + # libjpeg-turbo 3.x moved public headers under src/; jconfig.h is generated + # into the standalone build dir. jpeg-static is consumed as a pre-built + # IMPORTED library (built separately by build.sh — 3.x forbids add_subdirectory). + include_directories( + "../extern/libjpeg-turbo/src" + "${LIBJPEG_TURBO_BUILD_DIR}" + "${LIBJPEG_TURBO_BUILD_DIR}/src") + + add_library(jpeg-static STATIC IMPORTED) + set_target_properties(jpeg-static PROPERTIES + IMPORTED_LOCATION "${LIBJPEG_TURBO_BUILD_DIR}/libjpeg.a") add_executable(libjpegturbo12js jslib.cpp) diff --git a/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp b/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp index 7a15c920..7a531020 100644 --- a/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp +++ b/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp @@ -3,7 +3,10 @@ #pragma once +#include #include +#include +#include #include // #include "config.h" #include "jpeglib.h" @@ -117,43 +120,65 @@ class JPEGDecoder { jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, encoded_.data(), encoded_.size()); - // Read file header, set default decompression parameters + // Read the header. In libjpeg-turbo 3.x this is precision-agnostic. jpeg_read_header(&cinfo, TRUE); - // Force RGBA decoding, even for grayscale images - cinfo.out_color_space = JCS_EXT_RGBA; - jpeg_start_decompress(&cinfo); + // This codec handles single-component (grayscale) 12-bit JPEGs only. Fail + // closed on color input: forcing JCS_GRAYSCALE on a multi-component image + // would silently drop chroma and mis-report componentCount=1. + if (cinfo.num_components != 1) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error( + "Unsupported 12-bit JPEG: expected 1 component (grayscale), got " + + std::to_string(cinfo.num_components)); + } + // libjpeg-turbo 3.x is multi-precision in a single build; this codec only + // supports 12-bit samples. Reject other precisions rather than mis-decode. + if (cinfo.data_precision != 12) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error( + "Unsupported JPEG precision: expected 12-bit, got " + + std::to_string(cinfo.data_precision)); + } + + cinfo.out_color_space = JCS_GRAYSCALE; + jpeg_start_decompress(&cinfo); frameInfo_.width = cinfo.output_width; frameInfo_.height = cinfo.output_height; - frameInfo_.bitsPerSample = 8; - frameInfo_.componentCount = 1; //inColorspace == 2 ? 1 : 3; - - // Prepare output buffer - // int pixelFormat = (frameInfo_.componentCount == 1) ? TJPF_GRAY : TJPF_RGB; - - // const size_t destinationSize = frameInfo_.width * frameInfo_.height * tjPixelSize[pixelFormat]; - int pixelFormat = 1; - size_t output_size = cinfo.output_width * cinfo.output_height * pixelFormat; - - // std::vector output_buffer(output_size); + frameInfo_.bitsPerSample = 12; + frameInfo_.componentCount = 1; + + // One 12-bit sample per pixel, stored in a 16-bit-wide J12SAMPLE (short). + // Overflow-checked size (capped at 512 MiB of samples) so a malformed + // header cannot overflow the computation or force a huge allocation. + constexpr uint64_t kMaxOutputSamples = 512ull * 1024ull * 1024ull; + const uint64_t width64 = static_cast(cinfo.output_width); + const uint64_t height64 = static_cast(cinfo.output_height); + if (width64 == 0 || height64 == 0) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error("Invalid JPEG dimensions (zero width or height)"); + } + uint64_t output_size64 = width64 * height64; + if (output_size64 / width64 != height64 || output_size64 == 0 || + output_size64 > kMaxOutputSamples) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error("Decoded buffer size out of range"); + } + const size_t output_size = static_cast(output_size64); decoded_.resize(output_size); + const size_t stride = static_cast(cinfo.output_width); - auto stride = cinfo.output_width * pixelFormat; - - // Process data + // 12-bit precision decodes through jpeg12_read_scanlines with a + // J12SAMPARRAY (short-based) — the libjpeg-turbo 3.x per-precision API. + // decoded_ is std::vector, matching J12SAMPLE. while (cinfo.output_scanline < cinfo.output_height) { - int16_t* output_data = &decoded_[stride * cinfo.output_scanline]; - (void)jpeg_read_scanlines(&cinfo, &output_data, 1); + J12SAMPROW output_data = + reinterpret_cast(&decoded_[stride * cinfo.output_scanline]); + (void)jpeg12_read_scanlines(&cinfo, &output_data, 1); } jpeg_finish_decompress(&cinfo); - - // Step 7: release JPEG compression object - - // auto data = Uint8ClampedArray.new_(typed_memory_view(output_size, &output_buffer[0])); - - // This is an important step since it will release a good deal of memory. jpeg_destroy_decompress(&cinfo); } From 97a060b88140374c99567be3c7c92fce2d21abd9 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 8 Sep 2026 13:54:43 -0400 Subject: [PATCH 4/6] build: ignore the new build-libjpeg directory The two-stage build added build-libjpeg/ as the standalone libjpeg-turbo build tree, but only build/ and dist/ were ignored, so every local build left a few thousand untracked files in the tree. Both packages' .gitignore gains it (and a trailing newline, which neither had). --- packages/libjpeg-turbo-12bit/.gitignore | 3 ++- packages/libjpeg-turbo-8bit/.gitignore | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/libjpeg-turbo-12bit/.gitignore b/packages/libjpeg-turbo-12bit/.gitignore index a7e8e215..717dc13c 100644 --- a/packages/libjpeg-turbo-12bit/.gitignore +++ b/packages/libjpeg-turbo-12bit/.gitignore @@ -1,2 +1,3 @@ build -dist \ No newline at end of file +build-libjpeg +dist diff --git a/packages/libjpeg-turbo-8bit/.gitignore b/packages/libjpeg-turbo-8bit/.gitignore index a7e8e215..717dc13c 100644 --- a/packages/libjpeg-turbo-8bit/.gitignore +++ b/packages/libjpeg-turbo-8bit/.gitignore @@ -1,2 +1,3 @@ build -dist \ No newline at end of file +build-libjpeg +dist From bdd25d609bf488e433d2067a58b44055f58e1d31 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 8 Sep 2026 13:54:43 -0400 Subject: [PATCH 5/6] build(dist-size): rebaseline both libjpeg-turbo packages for 3.2.0 dist-size was the only failing check on this branch: 8 regressions, all in libjpeg-turbo-8bit. Measured from a docker:build in the CI toolchain image, which reproduced CI's numbers to within 0.1% (decode wasm +65.6% local against +65.5% on CI), so these are CI-equivalent figures as the checker's own instructions require. libjpeg-turbo-8bit grows and the growth is real, not a build mistake: libjpegturbowasm_decode.wasm 176.3 -> 292.0 KiB (+65.6%) libjpegturbowasm.wasm 438.4 -> 542.7 KiB (+23.8%) libjpegturbojs_decode.js 408.5 -> 624.2 KiB (+52.8%) libjpegturbojs.js 818.5 -> 1051.5 KiB (+28.5%) 3.x dropped WITH_12BIT and instantiates most of the codec once per precision instead: the build compiles jccolor-8/12/16.c, jcdiffct-8/12/16.c, jclossls-8/12.c and so on, and the resulting libturbojpeg.a carries 285 KB of 12- and 16-bit objects against 193 KB of 8-bit ones. 3.2.0 has no option to restrict which precisions are built (checked its CMakeLists: ENABLE_*, WITH_ARITH_*, WITH_JPEG7/8, WITH_SIMD, WITH_TURBOJPEG, WITH_TOOLS -- nothing for precision), and this package reaches libjpeg through the TurboJPEG API, whose single translation unit dispatches across precisions, so the linker cannot drop the copies this package will never use. The asm.js variants carry the same code as JavaScript, which is why they move too. Note the pair of measurements that did NOT get isolated: the library also went from an unspecified CMAKE_BUILD_TYPE (so -O0 for its own sources) to Release. Multi-precision is the mechanism the evidence above supports, but optimization level changed in the same step and no A/B was run to split the two. libjpeg-turbo-12bit shrinks sharply over the same upgrade, which is why it never tripped the gate: libjpegturbo12wasm.wasm 2185.6 -> 271.7 KiB (-87.6%) libjpegturbo12js.js 2493.4 -> 585.1 KiB (-76.5%) Its baseline is updated too, though the gate only fails on growth. Leaving it would let that package grow back to 2.1 MB unnoticed; the floor should be where the artifact actually is. Only these two packages are touched. The other six baseline entries are left alone deliberately: their local dists show sub-1% drift from unrelated builds, and folding that in would put noise in a diff whose whole purpose is making size changes visible in review. Correctness, same build: both package suites pass (21 tests), and the 12-bit decode test compares byte-for-byte against CT-512x512-12bit.raw, so the port to jpeg12_read_scanlines is pixel-exact rather than merely running. The generated-JS CSP gate passes on all six emitted files. Co-Authored-By: Claude Opus 5 --- tools/dist-size/baseline.json | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tools/dist-size/baseline.json b/tools/dist-size/baseline.json index 3661c665..c2f7113f 100644 --- a/tools/dist-size/baseline.json +++ b/tools/dist-size/baseline.json @@ -33,42 +33,42 @@ }, "libjpeg-turbo-12bit": { "libjpegturbo12js.js": { - "raw": 2553272, - "gzip": 266948 + "raw": 599145, + "gzip": 146341 }, "libjpegturbo12wasm.js": { - "raw": 112153, - "gzip": 28570 + "raw": 55354, + "gzip": 15020 }, "libjpegturbo12wasm.wasm": { - "raw": 2238031, - "gzip": 760773 + "raw": 278203, + "gzip": 93433 } }, "libjpeg-turbo-8bit": { "libjpegturbojs.js": { - "raw": 838153, - "gzip": 160171 + "raw": 1076703, + "gzip": 212348 }, "libjpegturbojs_decode.js": { - "raw": 418292, - "gzip": 118256 + "raw": 639223, + "gzip": 157179 }, "libjpegturbowasm.js": { - "raw": 57440, - "gzip": 15274 + "raw": 57640, + "gzip": 15292 }, "libjpegturbowasm.wasm": { - "raw": 448955, - "gzip": 96494 + "raw": 555769, + "gzip": 136985 }, "libjpegturbowasm_decode.js": { - "raw": 56350, - "gzip": 15191 + "raw": 56736, + "gzip": 15229 }, "libjpegturbowasm_decode.wasm": { - "raw": 180512, - "gzip": 70074 + "raw": 299002, + "gzip": 99458 } }, "libjxl": { From ab495631543de53bb8e19c93c94dd35119e445bd Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Tue, 8 Sep 2026 14:26:39 -0400 Subject: [PATCH 6/6] ci: raise the build job timeout to 50 minutes 20 was calibrated as ~5x the slowest leg then observed (libjxl, 239s). libjxl turns out to be far more variable than that single figure implied: on this PR its Build step ran 18m42s on an ordinary hosted runner and the job was cancelled at the bound, with dependencies restored from cache so the time went into the compile itself -- and with nothing under packages/libjxl changed, which a diff against main confirms. The same leg took 4m18s on #93 twenty minutes earlier. A bound set from a fast observation turns ordinary runner variance into a red check, and because GitHub records the result as `cancelled` rather than `failure` it costs a full CI cycle to tell apart from a real break. It also took every downstream job with it: test, dist-size and browser-smoke were all skipped, so the very check this PR exists to fix never ran. 50 keeps the property the bound was added for -- the unbounded `build (big-endian)` leg on #70 sat in_progress for 80+ minutes and would still be caught -- while leaving libjxl room to be slow and the emsdk image room to be cold. Only the build job changes; detect-changes, test, dist-size, browser-smoke and codspeed-walltime keep their bounds, none of which has been observed near its limit. release.yml sets no timeouts at all, so a slow libjxl cannot fail a release this way. Co-Authored-By: Claude Opus 5 --- .github/workflows/pr-checks.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 64b63b87..3421f398 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -191,9 +191,20 @@ jobs: # whole run, and every job downstream of it, pending until GitHub's 6-hour # default fires. # - # 20 is ~5x the slowest observed leg (libjxl, 239s) and leaves room for a - # cold emsdk image pull. - timeout-minutes: 20 + # 50, not the 20 this started at. That 20 was set as ~5x the slowest leg + # then observed (libjxl, 239s), but libjxl is far more variable than one + # observation suggested: on #79 its Build step ran 18m42s on an ordinary + # hosted runner — 4.5x that figure, and a cache hit on dependencies, so the + # time went into the compile itself — and the job was cancelled at the + # 20-minute bound despite the PR changing nothing under packages/libjxl. + # The same leg had taken 4m18s on #93 twenty minutes earlier. + # + # A bound calibrated to a fast observation converts ordinary runner + # variance into a red check, and a cancelled leg costs a full CI cycle to + # tell apart from a real failure. 50 still catches the wedge this exists + # for (the unbounded leg above ran 80+ minutes) and leaves room for both a + # slow libjxl compile and a cold emsdk image pull. + timeout-minutes: 50 runs-on: ubuntu-latest container: image: emscripten/emsdk:3.1.74