Skip to content

Commit 4c2e3f1

Browse files
fix delete session after tests to avoid resource leak
Previously sessions were not deleted, leaving device connections and port forwarding open. Now sessions are explicitly deleted, ensuring proper cleanup of resources.
1 parent 61f60d3 commit 4c2e3f1

File tree

5 files changed

+129
-3
lines changed

5 files changed

+129
-3
lines changed

src/server.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FastMCP } from 'fastmcp';
22
import registerTools from './tools/index.js';
33
import registerResources from './resources/index.js';
4+
import { hasActiveSession, safeDeleteSession } from './tools/sessionStore.js';
45

56
const server = new FastMCP({
67
name: 'Jarvis Appium',
@@ -11,4 +12,28 @@ const server = new FastMCP({
1112

1213
registerResources(server);
1314
registerTools(server);
15+
16+
// Handle client connection and disconnection events
17+
server.on("connect", (event) => {
18+
console.log("Client connected:", event.session);
19+
});
20+
21+
server.on("disconnect", async (event) => {
22+
console.log("Client disconnected:", event.session);
23+
// Only try to clean up if there's an active session
24+
if (hasActiveSession()) {
25+
try {
26+
console.log("Active session detected on disconnect, cleaning up...");
27+
const deleted = await safeDeleteSession();
28+
if (deleted) {
29+
console.log("Session cleaned up successfully on disconnect.");
30+
}
31+
} catch (error) {
32+
console.error("Error cleaning up session on disconnect:", error);
33+
}
34+
} else {
35+
console.log("No active session to clean up on disconnect.");
36+
}
37+
});
38+
1439
export default server;

src/tools/create-session.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
*/
44
import { z } from 'zod';
55
import fs from 'fs';
6-
import path from 'path';
76
import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver';
87
import { XCUITestDriver } from 'appium-xcuitest-driver';
9-
import { setSession, getDriver, getSessionId } from './sessionStore.js';
8+
import { setSession, hasActiveSession, safeDeleteSession } from './sessionStore.js';
109

1110
// Define capabilities type
1211
interface Capabilities {
@@ -44,6 +43,12 @@ export default function createSession(server: any): void {
4443
},
4544
execute: async (args: any, context: any): Promise<any> => {
4645
try {
46+
// Check if there's an existing session and clean it up first
47+
if (hasActiveSession()) {
48+
console.log('Existing session detected, cleaning up before creating new session...');
49+
await safeDeleteSession();
50+
}
51+
4752
const { platform, capabilities: customCapabilities } = args;
4853

4954
let defaultCapabilities: Capabilities;

src/tools/delete-session.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Tool to delete the current mobile session and clean up resources
3+
*/
4+
import { z } from 'zod';
5+
import { safeDeleteSession } from './sessionStore.js';
6+
7+
export default function deleteSession(server: any): void {
8+
server.addTool({
9+
name: 'delete_session',
10+
description: 'Delete the current mobile session and clean up resources.',
11+
parameters: z.object({}),
12+
annotations: {
13+
destructiveHint: true,
14+
readOnlyHint: false,
15+
openWorldHint: false,
16+
},
17+
execute: async (): Promise<any> => {
18+
try {
19+
const deleted = await safeDeleteSession();
20+
21+
if (deleted) {
22+
return {
23+
content: [
24+
{
25+
type: 'text',
26+
text: 'Session deleted successfully.',
27+
},
28+
],
29+
};
30+
} else {
31+
return {
32+
content: [
33+
{
34+
type: 'text',
35+
text: 'No active session found or deletion already in progress.',
36+
},
37+
],
38+
};
39+
}
40+
} catch (error: any) {
41+
console.error(`Error deleting session`, error);
42+
throw new Error(`Failed to delete session: ${error.message}`);
43+
}
44+
},
45+
});
46+
}

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
22
import answerAppium from './answerAppium.js';
33
import createSession from './create-session.js';
4+
import deleteSession from './delete-session.js';
45
import createCloudSession from './create-cloud-session.js';
56
import uploadApp from './upload-app.js';
67
import generateLocators from './locators.js';
@@ -19,6 +20,7 @@ import terminateApp from './interactions/terminateApp.js';
1920
export default function registerTools(server: FastMCP): void {
2021
selectPlatform(server);
2122
createSession(server);
23+
deleteSession(server);
2224
createCloudSession(server);
2325
uploadApp(server);
2426
generateLocators(server);

src/tools/sessionStore.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { XCUITestDriver } from 'appium-xcuitest-driver';
33

44
let driver: any = null;
55
let sessionId: string | null = null;
6+
let isDeletingSession = false; // Lock to prevent concurrent deletion
67

7-
export function setSession(d: any, id: string) {
8+
export function setSession(d: any, id: string | null) {
89
driver = d;
910
sessionId = id;
11+
// Reset deletion flag when setting a new session
12+
if (d && id) {
13+
isDeletingSession = false;
14+
}
1015
}
1116

1217
export function getDriver() {
@@ -17,6 +22,49 @@ export function getSessionId() {
1722
return sessionId;
1823
}
1924

25+
export function isDeletingSessionInProgress() {
26+
return isDeletingSession;
27+
}
28+
29+
export function hasActiveSession(): boolean {
30+
return driver !== null && sessionId !== null && !isDeletingSession;
31+
}
32+
33+
export async function safeDeleteSession(): Promise<boolean> {
34+
// Check if there's no session to delete
35+
if (!driver || !sessionId) {
36+
console.log('No active session to delete.');
37+
return false;
38+
}
39+
40+
// Check if deletion is already in progress
41+
if (isDeletingSession) {
42+
console.log('Session deletion already in progress, skipping...');
43+
return false;
44+
}
45+
46+
// Set lock
47+
isDeletingSession = true;
48+
49+
try {
50+
console.log('Deleting current session');
51+
await driver.deleteSession();
52+
53+
// Clear the session from store
54+
driver = null;
55+
sessionId = null;
56+
57+
console.log('Session deleted successfully.');
58+
return true;
59+
} catch (error) {
60+
console.error('Error deleting session:', error);
61+
throw error;
62+
} finally {
63+
// Always release lock
64+
isDeletingSession = false;
65+
}
66+
}
67+
2068
export const getPlatformName = (driver: any): string => {
2169
if (driver instanceof AndroidUiautomator2Driver) return 'Android';
2270
if (driver instanceof XCUITestDriver) return 'iOS';

0 commit comments

Comments
 (0)