Skip to content

GraphQL events page uses massive CPU? #2327

Open

Description

@benjie

Description

I went to the GraphQL events page, and although it doesn't seem to be doing anything (no visible changes), the fans on my PC span up and it seems to have pegged 16 of my cores to 60+% CPU each. The same does not happen on the graphql.org home page, it seems specific to the events page.

Steps to Reproduce

  1. Visit https://graphql.org/community/events in Firefox 147.0.2 under Ubuntu 25.10 running in a virt-manager virtual machine

Expected Result

Normal static web page uses almost no CPU.

Actual Result

Webpage consumes vast CPU:

Image

Additional Context

The VM likely does not have graphics acceleration, so maybe it's a WebGL thing? Even so, nothing seems to be changing on the page, so I'm not sure why this would happen.

Activity

  1. jeanlucaslima commented on Jun 29, 2026

    @jeanlucaslima
    Contributor

    Root cause

    The events page renders a custom WebGL map (src/app/(main)/community/events/map/engine.ts) that drives an unconditional render loop:

    // engine.ts:667
    private loop() {
      if (this.destroyed) return
      this.rafHandle = requestAnimationFrame(time => {
        // ...applyInertia / updateActiveMarkers / render...
        this.render()
        this.loop()        // ← re-schedules every frame, forever
      })
    }

    It does a full WebGL re-render every frame and immediately schedules the next one — even when the map is completely idle (no pan, no inertia, no marker animation). With hardware acceleration the GPU absorbs this; in a VM without GPU acceleration (software/llvmpipe), every frame's render runs on the CPU, which pegs many cores while nothing visibly changes. That matches the report exactly.

    Quantifying it

    While the page sits idle with no interaction, the map keeps calling requestAnimationFrame continuously — measured at ~124 calls/second (247 in a 2s window). A correct on-demand map would be ~0.

    How to test it programmatically

    Rather than measuring CPU directly (hardware/CI-dependent and flaky), assert the invariant that's actually broken: the render loop must go idle when nothing is animating. Counting engine requestAnimationFrame calls over an idle window is deterministic and GPU-independent, and drops into the existing test/e2e/community-events.spec.ts:

    test("map render loop goes idle when nothing is animating", async ({ page }) => {
      const mapContainer = page.locator("#meetups-map").first()
      await mapContainer.scrollIntoViewIfNeeded()
      await expect(mapContainer.locator("canvas").first()).toBeVisible({ timeout: 15_000 })
    
      // Let the intro/zoom animation and any inertia settle.
      await page.waitForTimeout(4000)
    
      // Count rAF calls over a fixed idle window, timed by setTimeout so the
      // measurement itself never drives the loop. No pointer interaction here.
      const framesWhileIdle = await page.evaluate(async () => {
        const orig = window.requestAnimationFrame.bind(window)
        let count = 0
        window.requestAnimationFrame = cb => { count++; return orig(cb) }
        await new Promise(r => setTimeout(r, 2000))
        window.requestAnimationFrame = orig
        return count
      })
    
      // Idle should be ~0; allow a few trailing frames.
      // Today this is ~240 → fails, documenting the bug. Passes once the loop is on-demand.
      expect(framesWhileIdle).toBeLessThan(10)
    })

    Suggested fix

    Make loop() schedule a new frame only while something is actually changing (active inertia, marker animations, cooling pointer trail, active drag, resize) and park the loop when idle, re-arming on the next interaction. The test above then goes green.

  2. jeanlucaslima commented on Jun 29, 2026

    @jeanlucaslima
    Contributor

    Sorry, this was an automated comment, but maybe another approach is having something simpler in place as well. But the issue is real

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions