预订工具里最糟的失败不是订不上。
是给两个人都发「您已预订成功」。 订不上可以再订一次;两个人都被告知订上了,接下来就得靠人打电话去摆平。
两个处理器,同一段描述
这个样例里有两个「占用时段」的处理器。用话描述完全一样 —看看空不空,空就占。
一个是这样。
// 1. Look.
final free = slot.takenBy == null;
// 2. Anything at all in here — a database round trip, a payment check, a
// log write, a lookup of the customer's name — hands control to
// whatever else was in flight.
await Future<void>.delayed(Duration.zero);
// 3. Write, using what was true in step 1.
if (free) { slot.takenBy = who; ... }
另一个是这样。
if (slot.takenBy != null) {
return '$at is already taken by ${slot.takenBy}';
}
slot.takenBy = who;
slot.confirmations.add(who);
await Future<void>.delayed(Duration.zero); // the slow part, after the claim
同一个 await,只是位置不同。 前者在「看」和「写」之间,后者在「写」之后。
而这个位置制造了重复预订。
先让它真的发生
unsafe -> Suh: "CONFIRMED 10:30 for Suh"
unsafe -> Park: "CONFIRMED 10:30 for Park"
unsafe result: 1 slot(s) confirmed to more than one person

画面精确地展示了现实中的症状。 时段的主人只有 Park 一个,而被告知「订上了」的人有两个。
这就是真实店铺里发生的样子。账上是一个人,门口来了两个。
所以服务器把所有发过确认的人都记着。
/// Everybody who has ever been told they got this slot. In a correct
/// booking system this never has more than one name in it, which is exactly
/// why it is worth keeping.
final confirmations = <String>[];
正常的话,它永远是一个名字的列表。永远只该有一条的列表,正是值得去数的列表。
然后让它不发生
同样两个人、同样的时段,只把那个缝隙关上。
--- cleared, same two people, same slot, gap closed ---
safe -> Suh: "CONFIRMED 10:30 for Suh"
safe -> Park: "10:30 is already taken by Suh"
safe result: nobody was told yes twice

拒绝不是「失败了」,而是「Suh 订走了」。 第二个人需要知道的不是自己失败了,而是这个位子没了 — 这样他才能去挑下一个时间。