Skip to content
← Free FDE training guides

Free learning / 40-minute concurrency proof lab

Coalesce cache misses without sharing the wrong promise

Make callers share one in-flight build, then challenge leader failure, a real waiter timeout, changed source input and separate workspace keys. Keep each caller's evidence distinct.

No account needed. Use your own copy for writing and keep the original as a reference.

Engineering lab library / Run locally

Choose your next agent lab.

Start with the durable workflow reference, then choose the boundary you want to test. Open its walkthrough for setup, commands and expected observations.

Synthetic records. No provider key or model inference. Runs locally with the standard library.

  1. Start here / Python 3.11+ · Standard library

    In-flight sharing, failure cleanup and waiter deadlines

    Compare duplicate builds with joined work, preserve a waiter's timeout and clear a failed leader before a later call.

    Python 3.11+ · Standard library · Owned threads · No network, model or database

Original LockedIn Labs workshop · 40 minutes. Python 3.11+ standard library. Follow function calls, dictionary keys and exceptions first. The program starts owned threads against synthetic in-memory inputs, holds their builders with explicit events, and joins every worker before reporting completion. No network, model, credentials, files, database or service deployment is involved.

The application caching guide introduces the reuse contract. This workshop executes a different question: what happens when callers ask for the same work before the first build finishes?

1. Name the unfinished work

A cache stampede is overlapping misses causing repeated work for the same candidate. Request coalescing, sometimes called singleflight, lets callers join an existing in-flight computation for a suitable key. The first participant starts the work; a waiter observes its eventual result or failure.

The Go singleflight package documents a practical duplicate-call suppression API. This exercise implements its own small Python test fixture; it does not run that package or claim the same complete API. It shares unfinished work only, retaining no completed-result cache. Registering an in-flight operation, caching its completed output and checking whether that output is valid now are separate jobs.

A Python Future represents a completion that can return a value or raise an exception. A bounded result(timeout=...) wait can raise TimeoutError while the work remains unfinished. Python's Future documentation separates waiting from cancellation. The fixture constructs Futures and manages their state as executor-like teaching machinery. In application code, use a maintained concurrency implementation rather than copying this small manager.

2. Follow one registration and two waits

The registry key is a tuple: supplied workspace, document and renderer version. Default callers both ask for North's guide-7 through renderer-1. These are fixture labels chosen by the driver, not authenticated callers or real tenants.

In FlightGroup.join, one lock covers finding an existing Future and registering a new one. The worker captures the source value under the input lock, then waits on a release event. The driver registers all callers while the build is held. This provides a reproducible overlap without using a sleep to guess which thread ran first. Thread scheduling still varies; do not infer a general ordering or CPU parallelism from this selected interleaving.

The visual explains six independently reset cases. It does not execute Python inside the browser. Predict both the build count and each caller's observation.

UNDERSTAND THE SYSTEM

Share one unfinished build without borrowing its guarantees

These six frames explain owned Python threads from the downloadable lab. Gates hold the build while callers join; a real bounded wait triggers the timeout case. The browser does not run the threads or measure service performance.

FOLLOW THE WORK

Watch one example, step by step.

6 authored stepsYou control the pace
01 / EXPLORE THE PARTSControls loading · written explanations below
Direction of information or workReturn / feedback

WHAT THIS PART DOES

Overlapping callers

The default burst has two callers for the same supplied workspace, document and renderer key. Their requests overlap because the builder is held, not because a sleep guesses their ordering.

CONNECTED TO
  • Register / join: In-flight registry
  • Wait / stop waiting: Build completion
Read the full worked example
  1. 01
    1 · Two misses can start the same work twice

    The naive case deliberately starts both builders before releasing either. Both capture the same revision and return values; the observed build count is two. Coalescing registers one Future and lets the overlapping caller join, so its first wave starts one build.

    ACTUAL THREAD EXECUTIONS: Naive first wave 2 · coalesced first wave 1

  2. 02
    2 · In-flight sharing is not result caching

    After the singleflight wave, the registry is empty. The fixture explicitly makes another same-key call. It starts another build and is not marked shared. Total builds become two. A separate completed-result cache needs its own reuse and publication contract.

    LATER INDEPENDENT CALL: Pending keys 0 · later shared false · total builds 2

  3. 03
    3 · A failed leader must release the key

    The held leader raises injected_builder_failure. Both callers observe builder-error, the registry becomes empty, and one explicit healthy later call starts fresh work. That later value is not a successful original attempt or an automatic retry loop.

    SHARED FAILURE + CLEANUP: Both caller errors · pending 0 · later value

  4. 04
    4 · A waiter timeout leaves the builder running

    The second caller's actual Future.result wait times out before the driver releases the builder. The shared work is still running. After release, the fixture observer can see a value, while the timed-out caller's recorded outcome remains wait-timeout.

    ACTUAL BOUNDED WAIT: Caller 2 wait-timeout · builder still running

  5. 05
    5 · Sharing does not refresh captured input

    The builder captures revision 1. The driver then changes the source to revision 2 before releasing it. Both callers receive the captured revision 1 from one build. Fewer executions have established neither current source validity nor publication safety.

    CONTROLLED SOURCE CHANGE: Builds 1 · returned r1 · current source r2

  6. 06
    6 · Different workspace keys need different work

    North and South both request guide-7 with renderer-1. Their full keys differ, so two builders start and each returns its own supplied workspace content. These are fixture labels, not authenticated tenants. Key separation is necessary here but cannot establish authorization.

    TWO DISTINCT KEYS: North build 1 · South build 1 · both unshared

Read the connections in plain English
  1. 01
    Overlapping callers → In-flight registry

    Both callers ask for the same trusted fixture key while the build is unfinished.

  2. 02
    In-flight registry → Owned builder

    Only the newly registered work starts an owned builder in the coalesced case.

  3. 03
    Owned builder → Build completion

    Publish one captured value or one explicit builder exception.

  4. 04
    Build completion → Overlapping callers

    Each observation can receive the value, receive the failure or stop waiting at its own deadline.

  5. 05
    Build completion → Captured candidate

    Keep source revision and scope with the result instead of treating shared completion as validation.

  6. 06
    Captured candidate → Receiving contract

    Inspect exact captured input, current source and the intended receiving task.

  7. 07
    In-flight registry → Receiving contract

    Completed entries disappear; another later call is independent work.

Check your understanding: Does a waiter's timeout prove the shared builder was cancelled?

No. The fixture observes the timeout while the build remains held and running. It then releases the worker and records a separate completion observation. The original caller outcome stays wait-timeout. Dependency cancellation and shared-work ownership need their own explicit policy and evidence.

3. Run six owned-thread cases

Download and read the runner. Keep an unchanged reference. Write predictions before reading its JSON:

Code example
python3 --version
python3 fde-cache-coalescing.py > coalescing-two.json
python3 fde-cache-coalescing.py --callers 3 > coalescing-three.json

The --callers argument accepts two or three. Separate-keys always uses two workspaces. Each case creates a fresh input set and registry. Gates and waits are bounded; the report is printed only after all owned builder threads join. These are actual function executions, not simulated arrival counts.

CaseFirst-wave builds with two callersObservation to defend
naive2Both misses execute the builder before either completes
singleflight1Second caller joins; a later same-key call starts build 2
leader-error1Both receive the injected failure; key clears and an explicit later call succeeds
waiter-timeout1Caller 2 stops waiting while shared work remains running
source-changed1Captured r1 is returned although the source has advanced to r2
separate-keys2North and South start separate work despite the same document ID

joined_existing records which registration reused an existing Future. It is not an authorization verdict or cache-hit rate. before_release describes the held first wave. after_first_wave precedes any separate later call. captured_builds retains the actual captured key and source body for each build. The event log orders registration/join/completion observations under one local lock; the captured inputs supply the separate build evidence.

4. Read the completion without erasing an earlier failure

Keep this ledger beside your predictions:

Text example
singleflight:
  joined_existing: [false, true]
  before_release.builds_started: 1
  after_first_wave.pending_keys: []
  later_call.shared: false
  later_call.builds_started: 2

leader-error:
  caller_outcomes: builder-error, builder-error
  first error: injected_builder_failure
  after_first_wave.pending_keys: []
  later_call.result.state: value

waiter-timeout:
  caller_outcomes[1].state: wait-timeout
  before_release.builder_still_running_after_waiter_timeout: true
  fixture_completion_observer[1].state: value
  caller_outcomes[1].value: null

source-changed:
  captured_builds[0].input.revision: 1
  caller_outcomes: revision 1, revision 1
  source_after.north.revision: 2

The timeout case executes a real short Future.result wait while the builder is held. The driver releases the worker only after observing TimeoutError. The timeout duration is a fixture setting, not a service latency measurement or a universal recommended deadline. The builder does not become cancelled because that wait ended.

After release, the fixture driver can inspect completion independently. It keeps that evidence in fixture_completion_observer; it does not rewrite the timed-out caller's caller_outcomes entry. A late response in a deployed system also needs an explicit receiving and reconciliation contract. Successful background work does not prove that the original caller received or accepted a result.

The failure case shares the authored RuntimeError with all first-wave callers. The registry removes completed work, including failure, before delivering its result. A separate healthy call then starts fresh work. It is deliberately one explicit new attempt; the program has no recursive retry policy. Permanent retention of a failed Future would strand later requests on a poisoned entry.

The fixture copies values when recording caller observations so one report does not mutate another's evidence. It is not a general mutable-object isolation API. Production sharing also needs an agreed result ownership/immutability contract.

5. Change the burst and challenge the boundary

With three callers, naive starts three builders and singleflight starts one first-wave builder. Both extra callers join in the coalesced case. The later independent singleflight call still makes total builds two. Leader-error now produces three first-wave errors. These execution counts establish no throughput, latency, model-token saving or hardware sizing result.

Use focused comparisons when receiving the work:

Code example
python3 fde-cache-coalescing.py --case waiter-timeout --callers 3
python3 fde-cache-coalescing.py --case source-changed
python3 fde-cache-coalescing.py --case separate-keys

In the three-caller timeout case, caller 2 keeps its timed-out observation; caller 3 receives the value after release. One caller ending its wait does not make the other waiter's decision for it. Cancellation policy must distinguish ending an individual wait, stopping shared work and cancelling a downstream operation. This exercise induces only the first of those.

Source-changed shares captured revision 1 with both callers after the driver advances the current source to revision 2. Coalescing did its narrow job: one build. It did not refresh input or make publication correct. Continue with the late-publication proof to inspect an authoritative publication fence. Current resource authorization still requires a separate trusted check before disclosure.

Separate-keys keeps North and South work apart. A document ID alone would not define this fixture's result. In a real application, derive scope in trusted code and identify every input that changes the result. Matching labels do not authenticate a workspace, and coalescing is not a tenant-isolation test.

The registry lives in one process. A second process or replica has its own registry; this lab supplies no cross-process suppression, durable lease, crash recovery, distributed lock or restart evidence. It also supplies no production admission bound on distinct keys, dependency execution timeout or resource authorization. The driver limits its own workload to two or three callers, and releases/joins its workers. Do not deploy this teaching manager.

6. Receive it as a delivery pod

Rotate three roles across another run:

  • Builder: preserve runtime, exact command, captured input and the event log. Identify the registration lock, cleanup boundary and one intentional failure.
  • Receiver: run the three-caller burst without author takeover. Explain one joined call, one failed or timed-out caller and one later independent attempt.
  • Application/platform owner: name trusted key derivation, current validity, wait ownership, dependency cancellation, distinct-key admission and the actual process/replica boundary. Choose one bounded receiving-environment experiment.

Keep individual observations and assistance separate from the team's explanation:

Text example
Learner / run / runtime / exact command:
Prediction before the run:
Requested key / joined existing work:
Actual first-wave builds / later builds:
My caller outcome / independent completion evidence:
Captured input revision / current source revision:
My contribution / assistance received:
Changed caller count / prediction / observed result:
Receiving action actually attempted / assistance:
Unresolved production boundary / accountable owner / next bounded experiment:

A receiver might next test an approved adapter with representative synthetic requests, one unavailable dependency and a separately defined wait policy. Document who owns each unresolved request after its caller stops waiting. Do not test real customer work merely because this local exercise completes.

7. Defend the mechanism in an interview

Explain: Why is singleflight not a cache? Use the empty post-wave registry and the later independent build as evidence.

Debug: A waiter times out, but another caller succeeds. Identify their shared work, separate observations and actual stop boundary before recommending a retry.

Design: Specify a trusted result key, current source/access checks, bounded waits, failure cleanup, mutable-result ownership and distinct-key admission. Explain the additional evidence needed across replicas and process failure.

Primary references checked 28 October 2026: Go singleflight and Python concurrent futures. The Python fixture, comparisons, visual frames and receiving exercise are original instruction. No Go package or production cache is activated.

Before you move on

Keep the evidence of your work.

  • Two- and three-caller build counts with original captured inputs.
  • Individual caller outcomes beside independent completion evidence.
  • A receiving record naming the actual process, validity and wait-ownership boundaries.

Keep these in your own working copy. Reading this page does not save work or record completion.

Suggested next free session

Caching 101: when can you reuse earlier work?

Keep source publication, authority and completed-result reuse separate from shared work. This is a suggested sequence; choose the practice that addresses your current gap.

Open the next free guide

Free account

Give your practice a home.

Try a role exercise with account drafts and a downloadable worksheet. Keep the files you made in this guide in your own working copy.

Create a free learning account

Guided training · by application

Build toward a program.

Request structured lessons and a training seat. Scope, schedule, feedback arrangements and tuition are confirmed in writing before you commit.

Explore a training application
Have Academy access?

Use your existing training seat to open worked explanations and practice in the related lesson. Creating a free account does not grant a seat.

Open the related Academy lesson →
Back to top ↑