C

Web › Module 6 › Lesson 4

BeginnerModule 6Lesson 4/6

Lab — Race Condition Lab

Simulate a one-time coupon race locally, then fix it with an atomic update

25 min+55 XP
Module progress4 of 6

Opening

Redeem twice — get paid twice?

Build a tiny in-memory coupon service, fire parallel redeems, observe double credit, then add an atomic guard. Use localhost only.

1. Step 1 — Vulnerable coupon API

race_lab.pyfrom flask import Flask, jsonify import threading, time app = Flask(__name__) uses = {"remaining": 1} lock = threading.Lock() @app.post("/redeem-unsafe") def redeem_unsafe(): remaining = uses["remaining"] time.sleep(0.05) # widen race window if remaining > 0: uses["remaining"] = remaining - 1 return jsonify(ok=True, credit=10) return jsonify(ok=False), 400 @app.post("/redeem-safe") def redeem_safe(): with lock: if uses["remaining"] <= 0: return jsonify(ok=False), 400 uses["remaining"] -= 1 return jsonify(ok=True, credit=10) if __name__ == "__main__": app.run(port=5060, threaded=True)

from flask import Flask, jsonify
import threading, time

app = Flask(__name__)
uses = {"remaining": 1}
lock = threading.Lock()

@app.post("/redeem-unsafe")
def redeem_unsafe():
    remaining = uses["remaining"]
    time.sleep(0.05)  # widen race window
    if remaining > 0:
        uses["remaining"] = remaining - 1
        return jsonify(ok=True, credit=10)
    return jsonify(ok=False), 400

@app.post("/redeem-safe")
def redeem_safe():
    with lock:
        if uses["remaining"] <= 0:
            return jsonify(ok=False), 400
        uses["remaining"] -= 1
        return jsonify(ok=True, credit=10)

if __name__ == "__main__":
    app.run(port=5060, threaded=True)

2. Step 2 — Parallel redeem

Reset remaining=1, then send two simultaneous POSTs to /redeem-unsafe (two terminals or a small script). Both may return ok=True. Repeat against /redeem-safe — only one should succeed.

Lab checklist

Production note

In-memory locks fail across multiple servers. Prefer database constraints and idempotency keys.

← Previous