Skip to content

Commit

Permalink
Create Server shutdown RPC service:
Browse files Browse the repository at this point in the history
- Add ServerControl related RPC to proto.
- Wire in ServerControl to scala server code.
- Wire ServerControl into typescript client code with stopServerGraceful and stopServerImmediate.

Closes #553
  • Loading branch information
shanedell committed Mar 8, 2023
1 parent 3f54f84 commit 3e0dd73
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 126 deletions.
96 changes: 66 additions & 30 deletions src/rpc/client/ts/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { getLogger } from './logger'
import { getClient } from './client'
import { Empty } from 'google-protobuf/google/protobuf/empty_pb'
import * as fs from 'fs'
import { ServerControlKind, ServerControlRequest } from './omega_edit_pb'

/**
* Artifact class
Expand Down Expand Up @@ -59,14 +60,14 @@ class Artifact {
* @param version version of the server package
* @param port port to listen on
* @param host interface to listen on
* @returns pid of the server process or undefined if the server failed to start
* @returns if the server started or failed
*/
export async function startServer(
rootPath: string,
version: string,
port: number = 9000,
host: string = '127.0.0.1'
): Promise<number | undefined> {
): Promise<boolean> {
// Set up the server
getLogger().debug({
fn: 'startServer',
Expand Down Expand Up @@ -101,7 +102,7 @@ export async function startServer(
output: 'silent',
})

// Return the server pid if it exists
// Return true if the server is running
return new Promise((resolve, reject) => {
if (server.pid !== undefined && server.pid) {
getLogger().debug({
Expand All @@ -110,9 +111,10 @@ export async function startServer(
port: port,
pid: server.pid,
})

// initialize the client
getClient(port, host)
resolve(server.pid)
resolve(true)
} else {
getLogger().error({
fn: 'startServer',
Expand All @@ -123,46 +125,80 @@ export async function startServer(
server: server,
},
})

reject(`Error getting server pid: ${server}`)
}
})
}

/**
* Stops the server gracefully
* @returns true if the server was stopped
*/
export function stopServerGraceful(): Promise<boolean> {
return new Promise<boolean>(async () => {
return stopServer(ServerControlKind.SERVER_CONTROL_GRACEFUL_SHUTDOWN)
})
}

/**
* Stops the server immediatly
* @returns true if the server was stopped
*/
export function stopServerImmediate(): Promise<boolean> {
return new Promise<boolean>(async () => {
return stopServer(ServerControlKind.SERVER_CONTROL_IMMEDIATE_SHUTDOWN)
})
}

/**
* Stop the server
* @param pid pid of the server process
* @param kind defines how the server should shutdown
* @returns true if the server was stopped
*/
export function stopServer(pid: number | undefined): boolean {
if (pid) {
getLogger().debug({ fn: 'stopServer', pid: pid })
try {
const result = process.kill(pid, 'SIGTERM')
getLogger().debug({ fn: 'stopServer', pid: pid, stopped: result })
return result
} catch (err) {
// @ts-ignore
if (err.code === 'ESRCH') {
function stopServer(kind: ServerControlKind): Promise<boolean> {
getLogger().debug({
fn: 'stopServer',
kind: kind.toString(),
})

return new Promise<boolean>((resolve, reject) => {
getClient().serverControl(
new ServerControlRequest().setKind(kind),
(err, resp) => {
if (err) {
getLogger().error({
fn: 'stopServer',
err: {
msg: err.message,
details: err.details,
code: err.code,
stack: err.stack,
},
})

return reject('stopServer error: ' + err.message)
}

if (resp.getResponseCode() != 0) {
getLogger().error({
fn: 'stopServer',
err: { msg: 'stopServer exit status: ' + resp.getResponseCode() },
})

return reject('stopServer error')
}

getLogger().debug({
fn: 'stopServer',
msg: 'Server already stopped',
pid: pid,
kind: kind.toString(),
stopped: true
})
return true
}
getLogger().error({
fn: 'stopServer',
err: { msg: 'Error stopping server', pid: pid, err: err },
})
return false
}
}

getLogger().error({
fn: 'stopServer',
err: { msg: 'Error stopping server, no PID' },
return resolve(true)
}
)
})
return false
}

/**
Expand Down
72 changes: 23 additions & 49 deletions src/rpc/client/ts/tests/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
*/

import { createSimpleFileLogger, getLogger, setLogger } from '../src/logger'
import { startServer, stopServer } from '../src/server'
import { stopServerImmediate, startServer } from '../src/server'
import { getClientVersion } from '../src/version'
import { setAutoFixViewportDataLength } from '../src/viewport'
import * as fs from 'fs'

// prettier-ignore
// @ts-ignore
Expand All @@ -30,21 +29,12 @@ import { testPort } from './specs/common'
const path = require('path')
const rootPath = path.resolve(__dirname, '..')

/**
* Gets the pid file for the given port
* @param port port to get the pid file for
* @returns path to the pid file
*/
function getPidFile(port: number): string {
return path.join(rootPath, `.test-server-${port}.pid`)
}

/**
* Mocha test fixture to setup the logger and start the server
* Mocha test fixture to setup to start the server
* @remarks used by mocha
*/
export async function mochaGlobalSetup(): Promise<number | undefined> {
const pidFile = getPidFile(testPort)
export async function mochaGlobalSetup(): Promise<boolean> {
const logFile = path.join(rootPath, 'test.log')
const level = process.env.OMEGA_EDIT_CLIENT_LOG_LEVEL || 'info'
const logger = createSimpleFileLogger(logFile, level)
Expand All @@ -60,57 +50,41 @@ export async function mochaGlobalSetup(): Promise<number | undefined> {
fn: 'mochaGlobalSetup',
msg: 'starting server',
port: testPort,
pidfile: getPidFile(testPort),
})

// don't fix viewport data length in tests
setAutoFixViewportDataLength(false)

const pid = await startServer(rootPath, getClientVersion(), testPort)
mochaGlobalTeardown()
if (pid) {
fs.writeFileSync(pidFile, pid.toString(), 'utf8')
}
const serverStarted = await startServer(
rootPath,
getClientVersion(),
testPort
)

getLogger().debug({
fn: 'mochaGlobalSetup',
msg: 'server started',
port: testPort,
pid: pid,
pidfile: getPidFile(testPort),
serverStarted: serverStarted,
})
return pid

return serverStarted
}

/**
* Mocha test fixture to stop the server
* @remarks used by mocha
*/
export function mochaGlobalTeardown(): boolean {
const pidFile = getPidFile(testPort)
getLogger().debug({
fn: 'mochaGlobalTeardown',
msg: 'stopping server',
port: testPort,
pidfile: pidFile,
})
if (fs.existsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').toString())
if (stopServer(pid)) {
fs.unlinkSync(pidFile)
getLogger().debug({
fn: 'mochaGlobalTeardown',
msg: 'server stopped',
port: testPort,
pid: pid,
})
return true
}
export async function mochaGlobalTeardown(): Promise<boolean> {
let stopped = await stopServerImmediate()

if (!stopped) {
getLogger().debug({
fn: 'mochaGlobalTeardown',
msg: 'failed to stop server',
port: testPort,
})
}
getLogger().debug({
fn: 'mochaGlobalTeardown',
msg: 'failed to stop server',
port: testPort,
pidfile: pidFile,
})
return false

return stopped
}
30 changes: 0 additions & 30 deletions src/rpc/client/ts/tests/specs/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,9 @@ import {
destroySession,
getSessionCount,
} from '../../src/session'
import { startServer, stopServer } from '../../src/server'
import { getClientVersion } from '../../src/version'
import * as fs from 'fs'

const path = require('path')
const rootPath = path.resolve(__dirname, '..', '..')
export const testPort = parseInt(process.env.OMEGA_EDIT_TEST_PORT || '9010')

function getPidFile(port: number): string {
return path.join(rootPath, `.test-server-${port}.pid`)
}

export async function startTestServer(
port: number
): Promise<number | undefined> {
const pid = await startServer(rootPath, getClientVersion(), port)
stopTestServer(port)
if (pid) {
fs.writeFileSync(getPidFile(port), pid.toString(), 'utf8')
}
return pid
}

export function stopTestServer(port: number): boolean {
const pidFile = getPidFile(port)
if (fs.existsSync(pidFile)) {
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').toString())
fs.unlinkSync(pidFile)
return stopServer(pid)
}
return false
}

export async function createTestSession(port: number) {
let session_id = ''
expect(await waitForReady(getClient(port)))
Expand Down
7 changes: 7 additions & 0 deletions src/rpc/client/ts/tests/specs/editing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ async function subscribeSession(
)
}
})
.on('error', (err) => {
// Call cancelled thrown when server is shutdown
if (!err.message.includes('Call cancelled')) {
throw err
}
})

return session_id
}

Expand Down
2 changes: 1 addition & 1 deletion src/rpc/client/ts/tests/specs/profiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { overwrite } from '../../src/change'

// prettier-ignore
// @ts-ignore
import { createTestSession, destroyTestSession, startTestServer, stopTestServer, testPort } from './common'
import { createTestSession, destroyTestSession, testPort } from './common'

describe('Profiling', () => {
let session_id = ''
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/client/ts/tests/specs/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {

// prettier-ignore
// @ts-ignore
import { createTestSession, destroyTestSession, startTestServer, stopTestServer, testPort } from './common'
import { createTestSession, destroyTestSession, testPort } from './common'

describe('Searching', () => {
let session_id = ''
Expand Down
14 changes: 14 additions & 0 deletions src/rpc/client/ts/tests/specs/stressTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ async function subscribeSession(
)
}
})
.on('error', (err) => {
// Call cancelled thrown when server is shutdown
if (!err.message.includes('Call cancelled')) {
throw err
}
})

return session_id
}

Expand Down Expand Up @@ -140,6 +147,13 @@ async function subscribeViewport(
)
}
})
.on('error', (err) => {
// Call cancelled thrown when server is shutdown
if (!err.message.includes('Call cancelled')) {
throw err
}
})

return viewport_id
}

Expand Down
2 changes: 1 addition & 1 deletion src/rpc/client/ts/tests/specs/undoRedo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { unlinkSync } from 'fs'

// prettier-ignore
// @ts-ignore
import { createTestSession, destroyTestSession, startTestServer, stopTestServer, testPort } from './common'
import { createTestSession, destroyTestSession, testPort } from './common'

describe('Undo/Redo', () => {
let session_id = ''
Expand Down
1 change: 0 additions & 1 deletion src/rpc/client/ts/tests/specs/version.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { getClient, waitForReady } from '../../src/client'

// prettier-ignore
// @ts-ignore
import { startTestServer, stopTestServer } from './common'

describe('Version', () => {
const port = 9010
Expand Down
7 changes: 7 additions & 0 deletions src/rpc/client/ts/tests/specs/viewport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ async function subscribeViewport(
)
}
})
.on('error', (err) => {
// Call cancelled thrown when server is shutdown
if (!err.message.includes('Call cancelled')) {
throw err
}
})

return viewport_id
}

Expand Down
Loading

0 comments on commit 3e0dd73

Please sign in to comment.