DVPPython Studio
Module 11: Operate under pressure / Build 1 of 4

Run concurrent checks without hiding failures

Bound concurrent I/O, preserve result order and clean up owned tasks before returning or raising.

Runs on your computer · 70–100 minutes · no paid services

Download practice filesFiles, commands & notes

Without JavaScript, use the step links and keep your files on your computer.

One useful idea

Calling an async def function creates a coroutine: it does not finish the work. await lets the event loop run it and resumes with its result. asyncio.run(main()) owns the event loop for a terminal program. Do not call it inside an already-running event loop. Two successive awaits are sequential; scheduled tasks can overlap while they await I/O. This is not CPU parallelism.

A Check stores a factory, such as Check("metadata", metadata), not metadata(). Validate the entire plan before starting effects. A semaphore admits at most 1–3 checks. Tasks wait for admission before their monotonic duration and timeout start; queue time is not check duration. Return results in input order, even if the last task finishes first.

An exact True becomes pass, False becomes fail, CheckUnavailable becomes unavailable and an elapsed deadline becomes timeout. Neither unavailable nor timeout is evidence of a pass. An internal TimeoutError before the deadline is a bug, not the scheduler’s timeout. A wrong return type or programming error must remain visible rather than turning into a successful empty result.

On a bug or caller cancellation, cancel and await all owned siblings. Use finally for resource cleanup and propagate CancelledError afterward. Do not interrupt a task already doing timeout cleanup a second time. These are cooperative deadlines: blocking code or nonterminating cleanup can outlast them. The supplied contracts and validate_plan help with preflight; scheduling, status mapping and cleanup in practice.py are your work.

Refresh first: Trace failures, Functions and return.

Trace a finished example

import asyncio
from operation_tools import Check, CheckUnavailable, run_checks

async def metadata():
    await asyncio.sleep(0.01)
    return True

async def provenance():
    raise CheckUnavailable("invented missing declaration")

async def qc():
    return False

async def main():
    results = await run_checks(
        [Check("metadata", metadata), Check("provenance", provenance),
         Check("qc", qc)], concurrency=2, timeout_seconds=1
    )
    print([(item.name, item.status) for item in results])
    print(all(item.status == "pass" for item in results))

asyncio.run(main())

Expected output

[('metadata', 'pass'), ('provenance', 'unavailable'), ('qc', 'fail')]
False

The slower first factory still occupies the first result slot. The missing declaration is unavailable, while the explicit False is fail. Durations are measured and vary; this example prints stable status/order only, not invented timings.

The finished implementation is in operation_tools/concurrency.py. Reading it is guided practice, not independent evidence.

Predict the order

qc finishes before metadata. Does qc become the first result?

Compare your answer · self-reviewed

No. Admission/completion order and the declared input order are different. Retain an index or ordered task list and return metadata, provenance, qc.

Explain cancellation

One child raises a programming error while another owns an open resource. Can the caller immediately abandon both?

Compare your answer · self-reviewed

No. Preserve the fault, cancel owned siblings and await their finally cleanup before propagating it. Do not convert cancellation into a passed receipt.

Find the bug

A factory returns "yes". Should bool(result) turn it into pass?

Compare your answer · self-reviewed

No. The contract requires an exact bool. Refuse the wrong return type visibly; coercion hides broken check implementations.

Change it, then build your own

One controlled change

Change qc to True, then change provenance to return False. Predict the three statuses and the aggregate before each run. Add a cooperatively delayed factory with a short timeout; do not block the event loop with time.sleep.

Your independent task

Implement run_checks in practice.py. Use the reviewed Check/CheckOutcome and validate_plan contracts, but own admission, monotonic timing, exact result mapping, elapsed versus internal timeout distinction and cancellation cleanup. Support the optional on_outcome callback after completed outcomes; callback bugs must remain visible. Do not delegate to the finished reference or alternative.

What success looks like

The build1 group passes changed status/order/admission, invalid-plan-before-effects and cleanup cases. Your own new input demonstrates a non-pass without losing successful siblings. A printed example alone is not completion.

Hint 1 · a question

Draw the lifecycle: validate, wait for admission, start clock, await factory, map result, clean up. Where does queue time end?

Hint 2 · a concept cue

An async with semaphore block bounds admission. Wrap only the admitted factory await in the timeout; keep output slots separate from completion order.

Hint 3 · a localized example

try/finally owns cleanup. Cancellation is a control signal, not an ordinary failed check. Study the reviewed tests for repeated cancellation before deciding which owned tasks still need cancellation.

Need the complete worked solution?

Open operation_tools/concurrency.py from the kit. Trace it, close it, then try fresh inputs in your own files. Treat the attempt as guided; seeing the solution does not award a practical pass.

Course help is guidance, not independent evidence. With JavaScript, opening help records guidance locally; otherwise note it in your README. Reset does not erase that history.

Repair a failed check

If the first result changes with timing, retain input indices. If a queued task times out before admission, move the timeout inside the semaphore. If an internal TimeoutError becomes timeout or a wrong type becomes pass, narrow the exception/type handling. If pending tasks remain, await owned cleanup before raising.

NotImplementedError means a practice stub is still unfinished. Read the failing test name and the last error line. Change one behavior, rerun that build, then rerun all implemented builds.

Show it works on new inputs

Write three fresh invented factories with different delays and one unavailable outcome. Retain your code, actual ordered output and explanation of the concurrency ceiling. Show one wrong-type fault and owned cleanup; state why this does not prove CPU parallelism or a hard deadline.

Self-review: name the input, result, refused case and reason. Your local test output and explanation are separate from a quiz score; this page does not certify a pass.

Keep the idea

Concurrency is resource ownership and failure policy, not just gathering results. A partial check remains partial; cleanup and truthful errors are part of the returned contract.