Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
TOCTOU prevention, distributed locking, idempotency keys, race condition detection for Node.js and serverless environments.
Concurrency Security
Patterns for preventing race conditions, double-execution, and state corruption in concurrent systems.
TOCTOU Prevention
Time-of-Check to Time-of-Use: the gap between reading state and acting on it.
// WRONG: check then act - another process can change state between linesconst balance = await db.accounts.findUnique({ where: { id } })
if (balance.amount >= amount) {
await db.accounts.update({ where: { id }, data: { amount: balance.amount - amount } })
}
// CORRECT: atomic check-and-update in a single statementconst updated = await db.$executeRaw`
UPDATE accounts
SET amount = amount - ${amount}
WHERE id = ${id} AND amount >= ${amount}
RETURNING *
`if (updated.count === 0) thrownew ()
Error
'Insufficient funds or concurrent update'
// File system TOCTOU (Node.js)// WRONGif (fs.existsSync(filePath)) {
fs.writeFileSync(filePath, data) // another process may have deleted it
}
// CORRECT: use O_EXCL flag for exclusive creationimport { open } from'fs/promises'try {
const fh = awaitopen(filePath, 'wx') // fail if file existsawait fh.writeFile(data)
await fh.close()
} catch (err: any) {
if (err.code === 'EEXIST') { /* already exists, handle */ }
throw err
}
Distributed Locking with Redis
importRedisfrom'ioredis'const redis = newRedis(process.env.REDIS_URL!)
// Simple SETNX + TTL lockasyncfunctionacquireLock(key: string, ttlMs: number): Promise<string | null> {
const token = crypto.randomUUID()
// SET key token NX PX ttlMs — atomic, returns OK or nullconst result = await redis.set(`lock:${key}`, token, 'NX', 'PX', ttlMs)
return result === 'OK' ? token : null
}
asyncfunctionreleaseLock(key: string, token: string): Promise<void> {
// Lua script: only delete if we own the lock (atomic compare-and-delete)const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
`await redis.eval(script, 1, `lock:${key}`, token)
}
// UsageasyncfunctionprocessPayment(paymentId: string) {
const token = awaitacquireLock(paymentId, 30_000) // 30s TTLif (!token) thrownewError('Payment already being processed')
try {
awaitdoPaymentWork(paymentId)
} finally {
awaitreleaseLock(paymentId, token)
}
}
Redlock Algorithm (multi-node)
importRedlockfrom'redlock'importRedisfrom'ioredis'// Connect to 3+ independent Redis nodes for Redlock quorumconst clients = [
newRedis('redis://redis1:6379'),
newRedis('redis://redis2:6379'),
newRedis('redis://redis3:6379'),
]
const redlock = newRedlock(clients, {
retryCount: 3,
retryDelay: 200,
retryJitter: 100,
})
asyncfunctioncriticalSection(resourceId: string) {
await redlock.using([`resource:${resourceId}`], 10_000, async (signal) => {
if (signal.aborted) throw signal.errorawaitperformAtomicOperation(resourceId)
if (signal.aborted) throw signal.error// check after long operations
})
}
// Middleware: extract idempotency key from header and dedup in DBimport { Request, Response, NextFunction } from'express'import { db } from'./db'exportasyncfunctionidempotencyMiddleware(req: Request, res: Response, next: NextFunction) {
const idempotencyKey = req.headers['idempotency-key'] asstring | undefinedif (!idempotencyKey || req.method === 'GET') returnnext()
// Look up existing resultconst existing = await db.idempotencyKeys.findUnique({
where: { key: idempotencyKey },
})
if (existing) {
// Return cached response — same status and bodyreturn res.status(existing.statusCode).json(existing.responseBody)
}
// Capture response to store itconst originalJson = res.json.bind(res)
res.json = (body: unknown) => {
// Store before sending
db.idempotencyKeys.create({
data: {
key: idempotencyKey,
statusCode: res.statusCode,
responseBody: body,
expiresAt: newDate(Date.now() + 24 * 60 * 60 * 1000), // 24h TTL
},
}).catch(console.error)
returnoriginalJson(body)
}
next()
}
-- DB schema for idempotency keysCREATE TABLE idempotency_keys (
key TEXT PRIMARY KEY,
status_code INTNOT NULL,
response_body JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON idempotency_keys (expires_at);
-- Clean up expired keys via pg_cron or a scheduled job
Atomic Database Operations
// SELECT FOR UPDATE: pessimistic lock on rowasyncfunctiondebitAccount(accountId: string, amount: number) {
await db.$transaction(async (tx) => {
const account = await tx.$queryRaw<Account[]>`
SELECT * FROM accounts WHERE id = ${accountId} FOR UPDATE
`if (!account[0] || account[0].balance < amount) {
thrownewError('Insufficient funds')
}
await tx.$executeRaw`
UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}
`
})
}
// UPSERT: atomic insert-or-update (no read-then-write gap)await db.$executeRaw`
INSERT INTO user_stats (user_id, login_count, last_login)
VALUES (${userId}, 1, NOW())
ON CONFLICT (user_id)
DO UPDATE SET
login_count = user_stats.login_count + 1,
last_login = NOW()
`
// Optimistic: version field — no lock held, conflict detected on saveinterfaceDocument {
id: stringcontent: stringversion: number
}
asyncfunctionupdateDocument(id: string, content: string, expectedVersion: number) {
const result = await db.$executeRaw`
UPDATE documents
SET content = ${content}, version = version + 1
WHERE id = ${id} AND version = ${expectedVersion}
`if (result === 0) thrownewError('Conflict: document was modified by another process')
}
// Pessimistic: FOR UPDATE — lock row for duration of transaction// Use when conflicts are frequent or the cost of retry is highasyncfunctionupdateDocumentPessimistic(id: string, content: string) {
await db.$transaction(async (tx) => {
await tx.$queryRaw`SELECT 1 FROM documents WHERE id = ${id} FOR UPDATE`await tx.$executeRaw`UPDATE documents SET content = ${content} WHERE id = ${id}`
})
}
Serverless Cold Start Race Conditions
// Problem: two cold starts may both try to initialize shared state// Solution: use atomic cloud primitives, not in-process flags// DynamoDB conditional write for distributed init lockimport { DynamoDB } from'@aws-sdk/client-dynamodb'const ddb = newDynamoDB({})
asyncfunctioninitializeOnce(jobId: string): Promise<boolean> {
try {
await ddb.putItem({
TableName: 'distributed-locks',
Item: { pk: { S: `init:${jobId}` } },
ConditionExpression: 'attribute_not_exists(pk)',
})
returntrue// this instance won the race
} catch (err: any) {
if (err.name === 'ConditionalCheckFailedException') returnfalse// another wonthrow err
}
}
Testing for Race Conditions
// Run the same operation N times in parallel and assert idempotencyasyncfunctiontestConcurrentDebit() {
const accountId = awaitcreateTestAccount({ balance: 100 })
const debitAmount = 100// Fire 10 concurrent debit requestsconst results = awaitPromise.allSettled(
Array.from({ length: 10 }, () =>debitAccount(accountId, debitAmount))
)
const successes = results.filter(r => r.status === 'fulfilled')
const failures = results.filter(r => r.status === 'rejected')
// Exactly one should succeedconsole.assert(successes.length === 1, `Expected 1 success, got ${successes.length}`)
console.assert(failures.length === 9, `Expected 9 failures, got ${failures.length}`)
const account = awaitgetAccount(accountId)
console.assert(account.balance === 0, `Balance should be 0, got ${account.balance}`)
}
Core rule: Every shared mutable resource needs either an atomic operation, a lock, or an idempotency guard. "It works in testing" is not enough — test with parallel load.