mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 22:22:39 +02:00
fix(api): the same game added to a run twice is one launch (#426)
Follow-up to #422, from the nesktop #3 review. POST /session/{id}/launch for a game that already has an open launch (requested or running, no stop requested) in the run now answers that launch with 200 instead of inserting a second one. The check runs under the run's row lock that addLaunch already takes, so two concurrent adds resolve to one launch. OpenAPI documents the 200. Tested: apps/api 208 pass against the local test Postgres, including a new test adding the same game twice concurrently.
This commit is contained in:
@@ -476,6 +476,11 @@ export namespace SessionApi {
|
||||
description:
|
||||
'Adds a launch to a run that its host has taken on and that is not being stopped; the host starts it in the box without stopping what already runs there. Answers with the launch: its `id` is what the launched game’s window carries, and what to stop when that window closes. A run that is waiting for a host, stopping or over is 409: ask for a new run instead. So is a run on a host that does not handle launches, with code `host_too_old`: stop the run and ask for a new one, as before launches. The run is still what is billed and what the allowance counts, so adding a launch asks nothing of either.',
|
||||
responses: {
|
||||
200: {
|
||||
content: { 'application/json': { schema: Result(Launch.Info) } },
|
||||
description:
|
||||
'That game already has a launch in this run that is not over: it is answered instead of a second one'
|
||||
},
|
||||
201: {
|
||||
content: { 'application/json': { schema: Result(Launch.Info) } },
|
||||
description: 'The launch has been requested'
|
||||
@@ -537,6 +542,8 @@ export namespace SessionApi {
|
||||
conflict('That run is not running, so it cannot take another launch');
|
||||
case 'hostTooOld':
|
||||
hostTooOld();
|
||||
case 'existing':
|
||||
return c.json({ data: result.launch }, 200);
|
||||
default:
|
||||
return c.json({ data: result.launch }, 201);
|
||||
}
|
||||
|
||||
@@ -226,6 +226,16 @@ describe('launches', () => {
|
||||
expect(run.launches.map((l: any) => l.state)).toEqual(['running', 'running']);
|
||||
});
|
||||
|
||||
test('the same game added twice is one launch', async () => {
|
||||
const { s, id, second } = await liveRun('launch-dedupe', 7109);
|
||||
const [a, b] = await Promise.all([addLaunch(s, id, second), addLaunch(s, id, second)]);
|
||||
const statuses = [a.status, b.status].sort();
|
||||
expect(statuses).toEqual([200, 201]);
|
||||
const ids = [((await a.json()) as any).data.id, ((await b.json()) as any).data.id];
|
||||
expect(ids[0]).toBe(ids[1]);
|
||||
expect((await read(s, id)).launches.length).toBe(2);
|
||||
});
|
||||
|
||||
test('a run that is not running takes no launch', async () => {
|
||||
const s = await scene('launch-notrunning', 7102);
|
||||
const { body } = await requestSession(s);
|
||||
|
||||
@@ -1230,7 +1230,8 @@ export namespace Session {
|
||||
| { outcome: 'notFound' }
|
||||
| { outcome: 'notRunning'; session: Info }
|
||||
| { outcome: 'hostTooOld' }
|
||||
| { outcome: 'created'; launch: Launch.Info };
|
||||
| { outcome: 'created'; launch: Launch.Info }
|
||||
| { outcome: 'existing'; launch: Launch.Info };
|
||||
|
||||
/**
|
||||
* Add a launch to a run that is going. The library and account checks are
|
||||
@@ -1266,6 +1267,16 @@ export namespace Session {
|
||||
return { outcome: 'notRunning', session: serialize(run) };
|
||||
}
|
||||
if (!(await Launch.hostTakesLaunches(run.id))) return { outcome: 'hostTooOld' };
|
||||
// The same game asked for twice (two windows, or a retry after a
|
||||
// lost reply) is one launch: under the run's lock the first add
|
||||
// has committed, so the second finds it and gets it back.
|
||||
const open = (await Launch.listForSession(run.id)).find(
|
||||
(l) =>
|
||||
l.gameId === input.gameId &&
|
||||
(Launch.OPEN as readonly string[]).includes(l.state) &&
|
||||
l.timeStopRequested === null
|
||||
);
|
||||
if (open) return { outcome: 'existing', launch: open };
|
||||
const launch = await Launch.insert({
|
||||
id: Identifier.ascending('launch'),
|
||||
sessionId: run.id,
|
||||
|
||||
Reference in New Issue
Block a user