Back to Feed
article 3m read

Device limit counts first and saves later, and the test never sends two at once

The licensing code I shipped checks how many devices a key already has, then inserts the new one in a separate step, so two activations racing for the last slot can both get in. The test activates devices one at a time and can't see it, a device racing itself gets a server error, and a key-only check passes for keys never bound anywhere. I found this by reading, not by running anything concurrent.

Article DepthPractice

Flow

Ways to count and save in one step

  1. 1Lock the key

    Hold a lock on the key while you count and save. Easy to follow, but bursts on one key wait in line.

  2. 2Counter with a condition

    Bump a counter only while it is under the cap. One step, but it has to come down when a device is removed.

The new licensing service has a cap on how many devices one key can run on. It works when people turn on one machine at a time. If two show up at once, I don't think the cap holds.

The code went in today as part of one huge commit, and I doubt it got a real look. I think an agent wrote most of it. When I read it back, the order of the steps bugged me.

Count first, save later

To turn on a device, the code counts the devices the key already has. It looks up the cap. It checks if this device is already on the key. If not, it compares the count to the cap and saves the new device.

The steps are fine. The order isn't. The count is taken at the start, and nothing stops it from going stale before the save. Two new devices that arrive together both see the same count, both see room, and both get saved. Now the key covers one machine too many.

The database won't catch it. Its only rule is that a device can't be on the same key twice.

The test would pass every time, because it turns devices on one at a time. That proves the cap works, not that it survives a crowd.

Two smaller holes

One device sending two requests at once trips the same gap. The database turns away the copy, which is right, but the code picks its error replies by reading message text, so the user gets a server error. The key check is weak too. Leave out the device ID, and a key that was never turned on anywhere still comes back good.

Doing the count and the save together

The check and the save need to be one step. You can lock the key while you count and save, or keep a counter and only bump it while it's under the cap:

UPDATE room SET used = used + 1 WHERE id = :room AND used < cap;

If no row changes, the key is full. Just remember to lower the counter when a device is removed. A duplicate from the same device should come back as a plain success.

I haven't tested any of this with requests landing together. It's all from reading the code, so I can't say yet how often two requests would really land in that gap.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Continue the thread

The live site's config gets rewritten when the admin test button runs

The agent gave my chore app a test runner that saves the config and puts it back after every test, then wired it to an admin button on the live server. It looks clean at the end of a run, but reading the code, visitors could get test settings mid-run, two overlapping runs could delete the real config and still report green, and a killed run loses the only saved copy.

via #testing · #concurrency