Skip to content

Tile whose glyph-range request fails is never rendered (silently) and the range is never retried #4430

Description

@mattackerman808

A tile whose glyph-range request fails is never rendered — silently — and the range is never retried

Platform: iOS (observed on 6.26.0 and 6.27.0, device + simulator); code path is core, so all platforms should be affected.

Summary

If the fetch for a glyph range fails (404, offline, misconfigured glyph URL), every vector tile whose labels reference that range permanently fails to render. MapLibre paints the overzoomed ancestor tile in its place, with no error surfaced beyond a single onGlyphsError log line. The failed range is also never re-requested for the lifetime of the process, so recovery is impossible even if the resource becomes available.

Real-world impact

We self-host glyphs with a partial range set (a common setup — openmaptiles/fonts subsets, offline bundles). Our style uses {name:latin}, and one bundled face was missing range 7680-7935 (Latin Extended Additional). Result: in San José, CA, every z14 tile containing a Vietnamese-named POI (e.g. "Phở 24") rendered as a blurry overzoomed z13 parent, while neighboring tiles with ASCII-only labels rendered normally. The geographic patchiness made this extremely hard to attribute — it presents as a data or cache problem, not a font problem. It took us two days and a per-request instrumentation harness to trace it to the one warning line.

Mechanism (v6.26.0 / current main)

GlyphManager::processResponse (src/mbgl/text/glyph_manager.cpp):

void GlyphManager::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) {
    if (res.error) {
        observer->onGlyphsError(fontStack, range, std::make_exception_ptr(std::runtime_error(res.error->message)));
        return;   // <-- request.req not cleared, requestors never notified
    }
    ...

Two consequences:

  1. The GlyphRequestors (the GeometryTileWorkers waiting on this range) are never notified, so the tile's symbol-layout dependency never resolves and the tile never becomes renderable. Fill/line layers that parsed fine are discarded along with the labels.
  2. request.req remains non-null, so requestRange's if (request.req) return; guard suppresses any future attempt to fetch the range — including after connectivity returns. (This is also the underlying mechanism of Node.js render hangs when repeating request after glyph load failure #3169, where a second render request hangs after a glyph failure.)

Expected behavior

A missing glyph range should degrade gracefully, consistent with how missing glyphs within a successfully-loaded range behave: affected labels are skipped, everything else in the tile renders. Concretely:

  • On error, notify requestors so dependent tiles complete layout without the unavailable glyphs.
  • Clear/mark the failed request so it can be retried (immediately on the next tile that needs it, or with backoff) rather than being pinned forever.

Reproduction

  1. Style with "glyphs" pointing at a server that 404s range 7680-7935 for the style's fontstack (serve 0-255 normally).
  2. A vector tile containing a symbol-layer label with any character in U+1E00–U+1EFF (e.g. Phở).
  3. Load the map over that tile at the tile's native zoom.

Observed: the tile never renders (ancestor shown); log shows one Failed to load glyph range 7680-7935 for font stack … line; the range is requested exactly once per process.

We're happy to submit a PR along the lines above (notify-requestors-on-error + retriable request state) if the approach sounds right to maintainers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions