File size: 3,943 Bytes
84aa3bf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import type { CommandModule } from 'yargs';
import fs from 'node:fs';
import chalk from 'chalk';
import { debugLogger } from '@google/gemini-cli-core';
import { exitCli } from '../utils.js';
import { DEFAULT_PORT, getPidFilePath } from './constants.js';
import {
  getBinaryPath,
  isExpectedLiteRtServerProcess,
  isProcessRunning,
  isServerRunning,
  readServerPid,
  readServerProcessInfo,
  resolveGemmaConfig,
} from './platform.js';

export type StopServerResult =
  | 'stopped'
  | 'not-running'
  | 'unexpected-process'
  | 'failed';

export async function stopServer(
  expectedPort?: number,
): Promise<StopServerResult> {
  const processInfo = readServerProcessInfo();
  const pidPath = getPidFilePath();

  if (!processInfo) {
    return 'not-running';
  }

  const { pid } = processInfo;
  if (!isProcessRunning(pid)) {
    debugLogger.log(
      `Stale PID file found (PID ${pid} is not running), removing ${pidPath}`,
    );
    try {
      fs.unlinkSync(pidPath);
    } catch {
      // ignore
    }
    return 'not-running';
  }

  const binaryPath = processInfo.binaryPath ?? getBinaryPath();
  const port = processInfo.port ?? expectedPort;
  if (!isExpectedLiteRtServerProcess(pid, { binaryPath, port })) {
    debugLogger.warn(
      `Refusing to stop PID ${pid} because it does not match the expected LiteRT server process.`,
    );
    return 'unexpected-process';
  }

  try {
    process.kill(pid, 'SIGTERM');
  } catch {
    return 'failed';
  }

  await new Promise((resolve) => setTimeout(resolve, 1000));

  if (isProcessRunning(pid)) {
    try {
      process.kill(pid, 'SIGKILL');
    } catch {
      // ignore
    }
    await new Promise((resolve) => setTimeout(resolve, 500));
    if (isProcessRunning(pid)) {
      return 'failed';
    }
  }

  try {
    fs.unlinkSync(pidPath);
  } catch {
    // ignore
  }

  return 'stopped';
}

export const stopCommand: CommandModule = {
  command: 'stop',
  describe: 'Stop the LiteRT-LM server',
  builder: (yargs) =>
    yargs.option('port', {
      type: 'number',
      description: 'Port where the LiteRT server is running',
    }),
  handler: async (argv) => {
    let port: number | undefined;
    if (argv['port'] !== undefined) {
      port = Number(argv['port']);
    }

    if (!port) {
      const { configuredPort } = resolveGemmaConfig(DEFAULT_PORT);
      port = configuredPort;
    }

    const processInfo = readServerProcessInfo();
    const pid = processInfo?.pid ?? readServerPid();

    if (pid !== null && isProcessRunning(pid)) {
      debugLogger.log(`Stopping LiteRT server (PID ${pid})...`);
      const result = await stopServer(port);
      if (result === 'stopped') {
        debugLogger.log(chalk.green('LiteRT server stopped.'));
        await exitCli(0);
      } else if (result === 'unexpected-process') {
        debugLogger.error(
          chalk.red(
            `Refusing to stop PID ${pid} because it does not match the expected LiteRT server process.`,
          ),
        );
        debugLogger.error(
          chalk.dim(
            'Remove the stale pid file after verifying the process, or stop the process manually.',
          ),
        );
        await exitCli(1);
      } else {
        debugLogger.error(chalk.red('Failed to stop LiteRT server.'));
        await exitCli(1);
      }
      return;
    }

    const running = await isServerRunning(port);
    if (running) {
      debugLogger.log(
        chalk.yellow(
          `A server is responding on port ${port}, but it was not started by "gemini gemma start".`,
        ),
      );
      debugLogger.log(
        chalk.dim(
          'If you started it manually, stop it from the terminal where it is running.',
        ),
      );
      await exitCli(1);
    } else {
      debugLogger.log('No LiteRT server is currently running.');
      await exitCli(0);
    }
  },
};