Skip to content

Commit

Permalink
Reuse port on mutiny-app restarts where possible #12
Browse files Browse the repository at this point in the history
  • Loading branch information
caolan committed Sep 9, 2024
1 parent 919e203 commit 3e352e1
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 16 deletions.
15 changes: 14 additions & 1 deletion lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export type MutinyRequest = {
export type MutinyRequestBody = {type: "LocalPeerId"}
| {type: "Peers"}
| {type: "AppAnnouncements"}
| {type: "GetLastPort", app_uuid: string}
| {type: "SetLastPort", app_uuid: string, port: number}
| {type: "AppInstanceUuid", label: string}
| {type: "CreateAppInstance", label: string}
| {type: "Announce", peer: string, app_uuid: string, data: JsonValue}
Expand Down Expand Up @@ -112,6 +114,7 @@ export type MutinyResponseBody = {type: "Success"}
| {type: "LocalPeerId", peer_id: string}
| {type: "Peers", peers: string[]}
| {type: "AppInstanceUuid", uuid: string | null}
| {type: "GetLastPort", port: number | null}
| {type: "CreateAppInstance", uuid: string}
| {type: "Message", message: Message}
| {type: "InboxMessages", messages: Message[]}
Expand Down Expand Up @@ -281,6 +284,17 @@ export class MutinyClient {
return response.uuid;
}

async getLastPort(app_uuid: string): Promise<number | null> {
const response = await this.requestOne({type: "GetLastPort", app_uuid});
assert(response.type === 'GetLastPort');
return response.port;
}

async setLastPort(app_uuid: string, port: number): Promise<void> {
const response = await this.requestOne({type: "SetLastPort", app_uuid, port});
assert(response.type === 'Success');
}

async createAppInstance(label: string): Promise<string> {
const response = await this.requestOne({type: "CreateAppInstance", label});
assert(response.type === 'CreateAppInstance');
Expand All @@ -290,7 +304,6 @@ export class MutinyClient {
async announce(peer: string, app_uuid: string, data: JsonValue): Promise<void> {
const response = await this.requestOne({type: "Announce", peer, app_uuid, data});
assert(response.type === 'Success');
return;
}

async announcements(): Promise<AppAnnouncement[]> {
Expand Down
2 changes: 1 addition & 1 deletion mutiny-app/mutiny-app
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ DIR=`dirname $0`
deno run \
--allow-read \
--allow-write \
--allow-net="127.0.0.1:0" \
--allow-net="127.0.0.1" \
--allow-env="XDG_RUNTIME_DIR,HOME" \
$DIR/src/main.ts \
$@
Expand Down
47 changes: 33 additions & 14 deletions mutiny-app/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,19 +117,38 @@ export class Server {
serveDir(request, {fsRoot: this.root})
}

serve(): Deno.HttpServer<Deno.NetAddr> {
return Deno.serve({
hostname: '127.0.0.1',
port: 0,
onListen: addr => {
console.log("Application:");
console.log(` uuid: ${this.app.uuid}`);
console.log(` label: ${this.app.label}`);
console.log("");
console.log(`Serving ${this.root}:`);
console.log(` http://${addr.hostname}:${addr.port}/`);
},
}, this.handleRequest.bind(this));
async serve(): Promise<Deno.HttpServer<Deno.NetAddr>> {
const onListen = async (addr: Deno.NetAddr) => {
console.log("Application:");
console.log(` uuid: ${this.app.uuid}`);
console.log(` label: ${this.app.label}`);
console.log("");
console.log(`Serving ${this.root}:`);
console.log(` http://${addr.hostname}:${addr.port}/`);
// Update last used port so we can attempt to use it again
// on restart.
await this.client.setLastPort(this.app.uuid, addr.port);
};
const hostname = '127.0.0.1';
const port = await this.client.getLastPort(this.app.uuid) ?? 0;
try {
return Deno.serve({
onListen,
hostname,
port,
}, this.handleRequest.bind(this));
} catch (err) {
if (err.code === 'EADDRINUSE') {
// Address already in use, get a new randomly assigned port
return Deno.serve({
onListen,
hostname,
port: 0,
}, this.handleRequest.bind(this));
} else {
throw err;
}
}
}
}

Expand All @@ -153,5 +172,5 @@ if (import.meta.main) {
);

const server = new Server(client, {label, uuid}, root);
server.serve();
await server.serve();
}
10 changes: 10 additions & 0 deletions mutinyd/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ pub enum RequestBody {
AppInstanceUuid {
label: String,
},
GetLastPort {
app_uuid: String,
},
SetLastPort {
app_uuid: String,
port: u16,
},
LocalPeerId,
Peers,
Announce {
Expand Down Expand Up @@ -69,6 +76,9 @@ pub enum ResponseBody {
AppInstanceUuid {
uuid: Option<String>,
},
GetLastPort {
port: Option<u16>,
},
LocalPeerId {
peer_id: String
},
Expand Down
15 changes: 15 additions & 0 deletions mutinyd/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,21 @@ impl Server {
let uuid = self.get_app_uuid(&label)?;
let _ = request.response.send(ResponseBody::AppInstanceUuid {uuid}).await;
},
RequestBody::GetLastPort {app_uuid} => {
let tx = self.store.transaction()?;
let peer_id = tx.get_or_put_peer(&self.peer_id.to_base58())?;
let app_id = tx.get_or_put_app(peer_id, &app_uuid)?;
let port = tx.get_last_port(app_id)?;
let _ = request.response.send(ResponseBody::GetLastPort {port}).await;
},
RequestBody::SetLastPort {app_uuid, port} => {
let tx = self.store.transaction()?;
let peer_id = tx.get_or_put_peer(&self.peer_id.to_base58())?;
let app_id = tx.get_or_put_app(peer_id, &app_uuid)?;
tx.set_last_port(app_id, port)?;
tx.commit()?;
let _ = request.response.send(ResponseBody::Success).await;
},
RequestBody::LocalPeerId => {
let _ = request.response.send(ResponseBody::LocalPeerId {
peer_id: self.swarm.local_peer_id().to_base58()
Expand Down
30 changes: 30 additions & 0 deletions mutinyd/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ impl<'a> StoreTransaction<'a> {
PRAGMA user_version = 3;"
)?;
},
3 => {
// Re-use of randomly assigned ports
println!("Migrating database to version 4");
self.tx.execute_batch(
"CREATE TABLE app_last_port (
app_id INTEGER PRIMARY KEY REFERENCES app(id),
port INTEGER NOT NULL
);
PRAGMA user_version = 4;"
)?;
},
_ => break,
}
}
Expand Down Expand Up @@ -203,6 +214,25 @@ impl<'a> StoreTransaction<'a> {
Ok(())
}

pub fn get_last_port(&self, app_id: i64) -> Result<Option<u16>> {
let mut stmt = self.tx.prepare_cached(
"SELECT port
FROM app_last_port
WHERE app_id = ?1",
)?;
stmt.query_row([app_id], |row| row.get::<_, u16>(0)).optional()
}

pub fn set_last_port(&self, app_id: i64, port: u16) -> Result<()> {
let mut stmt = self.tx.prepare_cached(
"INSERT INTO app_last_port (app_id, port)
VALUES (?1, ?2)
ON CONFLICT (app_id) DO UPDATE SET port=?2",
)?;
stmt.execute(params![app_id, port])?;
Ok(())
}

pub fn get_peer(&self, peer_id: &str) -> Result<Option<i64>> {
let mut stmt = self.tx.prepare_cached(
"SELECT id
Expand Down

0 comments on commit 3e352e1

Please sign in to comment.