Image Extension 3.0.1.3-SNAPSHOT — up to 67% faster, 99% fewer exceptions!

The image extension just got a lot leaner. We profiled it with JFR and found some juicy stuff — redundant Tika MIME detection, exception-driven control flow, double writes, and a race condition in member function dispatch.

Requires Lucee 6.2.5+

Catch-up: what happened since 3.0.0.9

A few releases went out without announcements, so here’s the summary:

3.0.1.0 — Lucee 6 + 7 compat

  • Added javax servlet support with dual TLD entries for Lucee 6 compatibility (LDEV-6120)
  • Note: the initial javax implementation was broken — use 3.0.1.1+

3.0.1.1 — jakarta/javax fix

  • LDEV-6157<cfimage action="writeToBrowser"> threw NoSuchMethodError on Lucee 6 because touchDestination() bytecode was bound to jakarta servlet API
  • CI now builds once, tests against Lucee 6.2 + 7.0 (stable + snapshot), Java 11 + 21

3.0.1.2 — CVE fix

  • LDEV-5129 — removed unused bundled jars including commons-io (CVE-2024-47554)

3.0.1.3 — performance + bug fixes

Three tickets, nine commits:

  • LDEV-6243 — Fixed a race condition where img.getWidth() and other member functions would intermittently fail with Can't cast Object type [Struct] to a value of type [image] under concurrent requests. ~1% failure rate, now zero. BIF equivalents (imageGetWidth()) were never affected.

  • LDEV-6244 — The extension was calling GetApplicationSettings() via reflection on every image operation to read coder config. This triggered DummyWSHandler class load exceptions internally — 128,000 exceptions in a 130-second benchmark! Replaced with pc.getApplicationContext().getCustom("image"), same approach the S3 extension uses.

  • LDEV-6245 — Five performance fixes:

    • Removed a redundant static coder cache that was bypassed by if (true || (since 2022)
    • Fixed a missing return in AImageIOInterface.write() that caused every file write through TwelveMonkeys to encode and write the image twice
    • Stopped throwing and catching 96,000 IOExceptions for normal format matching control flow
    • Apache Tika MIME detection was running 3-4 times per imageRead (once per coder). Now runs once and passes the result down

Benchmarks

5,000 iterations, inspect never, Lucee 7.1.0.82-SNAPSHOT, Java 21:

Test 3.0.1.2 3.0.1.3 Improvement
imageRead (PNG) 872/s 1,230/s +41%
imageWrite (PNG→JPG) 272/s 453/s +67%
imageInfo 801/s 1,093/s +36%
imageWrite (PNG) 306/s 333/s +9%
imageResize 250/s 270/s +8%

The read-heavy operations improved the most because that’s where the overhead was — Tika detection, exception handling, and the GetApplicationSettings BIF call. Resize and write are more modest because they’re dominated by actual pixel work.

Exception count: 354,332 → 2,309 (99.3% reduction)

Less exceptions = less GC pressure = more consistent throughput. GC counts dropped across the board (e.g. imageRead: 37 → 21 GCs per 5k ops).

JFR profile — before and after

Before: Tika MIME detection was nearly as expensive as the actual image resize (~14k CPU samples). GetApplicationSettings and DummyWSHandler exception handling dominated the profile.

After: The top of the profile is now actual image work — TwelveMonkeys resize and PNG decode/encode. As it should be.

1 Like