Skip to content

Commit

Permalink
fix: forcefully shutdown ftl from extension (#1672)
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman authored Jun 6, 2024
1 parent c9d63f8 commit 3608866
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 176 deletions.
2 changes: 1 addition & 1 deletion examples/go/echo/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.2

replace github.com/TBD54566975/ftl => ../../..

require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000
require github.com/TBD54566975/ftl v0.241.2

require (
connectrpc.com/connect v1.16.1 // indirect
Expand Down
64 changes: 64 additions & 0 deletions extensions/vscode/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
'semi': ['error', 'never'],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': true }],
'jsx-quotes': ['error', 'prefer-single'],
'no-trailing-spaces': 'error',
'no-multiple-empty-lines': ['error', { 'max': 1, 'maxEOF': 0, 'maxBOF': 0 }],
'eol-last': ['error', 'always'],
'max-len': ['error', { 'code': 120, 'tabWidth': 4, 'ignoreUrls': true, 'ignoreComments': false, 'ignoreRegExpLiterals': true, 'ignoreStrings': true, 'ignoreTemplateLiterals': true }],
'func-style': ['error', 'expression'],
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/ban-ts-comment': [
2,
{
'ts-ignore': 'allow-with-description',
},
],
'indent': ['error', 2, {
'SwitchCase': 1,
'VariableDeclarator': 1,
'outerIIFEBody': 1,
'MemberExpression': 1,
'FunctionDeclaration': {
'parameters': 1,
'body': 1
},
'FunctionExpression': {
'parameters': 1,
'body': 1
},
'CallExpression': {
'arguments': 1
},
'ArrayExpression': 1,
'ObjectExpression': 1,
'ImportDeclaration': 1,
'flatTernaryExpressions': false,
'ignoreComments': false
}],
},
}
30 changes: 0 additions & 30 deletions extensions/vscode/.eslintrc.json

This file was deleted.

4 changes: 2 additions & 2 deletions extensions/vscode/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"publisher": "ftl",
"description": "VSCode extension for FTL",
"icon": "images/icon.png",
"version": "0.0.6",
"version": "0.1.0",
"repository": {
"type": "git",
"url": "https://github.com/TBD54566975/ftl-vscode"
Expand Down
138 changes: 138 additions & 0 deletions extensions/vscode/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as vscode from 'vscode'
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
} from 'vscode-languageclient/node'
import { FTLStatus } from './status'

export class FTLClient {
private clientName = 'ftl languge server'
private clientId = 'ftl'

private statusBarItem: vscode.StatusBarItem
private outputChannel: vscode.OutputChannel
private client: LanguageClient | undefined
private isClientStarting = false

constructor(statusBar: vscode.StatusBarItem, output: vscode.OutputChannel) {
this.statusBarItem = statusBar
this.outputChannel = output
}

public async start(ftlPath: string, cwd: string, flags: string[], context: vscode.ExtensionContext) {
if (this.client || this.isClientStarting) {
this.outputChannel.appendLine('FTL client already running or starting')
return
}
this.isClientStarting = true

this.outputChannel.appendLine('FTL extension activated')

const serverOptions: ServerOptions = {
run: {
command: `${ftlPath}`,
args: ['dev', ...flags],
options: { cwd: cwd }
},
debug: {
command: `${ftlPath}`,
args: ['dev', ...flags],
options: { cwd: cwd }
},
}

this.outputChannel.appendLine(`Running ${ftlPath} with flags: ${flags.join(' ')}`)
console.log(serverOptions.debug.args)

const clientOptions: LanguageClientOptions = {
documentSelector: [
{ scheme: 'file', language: 'kotlin' },
{ scheme: 'file', language: 'go' },
],
outputChannel: this.outputChannel,
}

this.client = new LanguageClient(
this.clientId,
this.clientName,
serverOptions,
clientOptions
)

context.subscriptions.push(this.client)

this.outputChannel.appendLine('Starting lsp client')
try {
await this.client.start()
this.outputChannel.appendLine('Client started')
console.log(`${this.clientName} started`)
FTLStatus.started(this.statusBarItem)
} catch (error) {
console.error(`Error starting ${this.clientName}: ${error}`)
FTLStatus.error(this.statusBarItem, `Error starting ${this.clientName}: ${error}`)
this.outputChannel.appendLine(`Error starting ${this.clientName}: ${error}`)
}

this.isClientStarting = false
}

public async stop() {
if (!this.client && !this.isClientStarting) {
return
}

const timeout = 10000 // 10 seconds
if (this.isClientStarting) {
this.outputChannel.appendLine(`Waiting for client to complete startup before stopping`)
const startWaitTime = Date.now()
while (this.isClientStarting) {
await new Promise(resolve => setTimeout(resolve, 100))
if (Date.now() - startWaitTime > timeout) {
this.outputChannel.appendLine(`Timeout waiting for client to start`)
break
}
}
}

console.log('Stopping client')
const serverProcess = this.client!['_serverProcess']

try {
await this.client!.stop()
await this.client!.dispose()
this.client = undefined
console.log('Client stopped')
} catch (error) {
console.error('Error stopping client', error)
}

console.log('Stopping server process')
if (serverProcess && !serverProcess.killed) {
try {
process.kill(serverProcess.pid, 'SIGTERM')
// Wait a bit to see if the process terminates
await new Promise(resolve => setTimeout(resolve, 1000))

if (!serverProcess.killed) {
console.log('Server process did not terminate with SIGTERM, trying SIGKILL')
process.kill(serverProcess.pid, 'SIGKILL')
console.log('Server process terminated with SIGKILL')
}
} catch (error) {
console.log('SIGTERM failed, trying SIGKILL', error)
try {
// Forcefully terminate if SIGTERM fails
process.kill(serverProcess.pid, 'SIGKILL')
console.log('Server process terminiated with SIGKILL')
} catch (killError) {
console.log('Failed to kill server process', killError)
}
}
} else if (serverProcess && serverProcess.killed) {
console.log('Server process was already killed')
}

FTLStatus.stopped(this.statusBarItem)
}
}
20 changes: 10 additions & 10 deletions extensions/vscode/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import * as vscode from "vscode"
import * as fs from 'fs'
import * as vscode from 'vscode'
import * as path from 'path'
import { exec, execSync } from 'child_process'
import { exec } from 'child_process'
import { promisify } from 'util'
import semver from 'semver'
import { lookpath } from "lookpath"
import { lookpath } from 'lookpath'

export const MIN_FTL_VERSION = '0.169.0'

const execAsync = promisify(exec)

export const getProjectOrWorkspaceRoot = async (): Promise<string> => {
const workspaceFolders = vscode.workspace.workspaceFolders
if (!workspaceFolders) {
vscode.window.showErrorMessage("FTL extension requires an open folder or workspace to work correctly.")
return ""
vscode.window.showErrorMessage('FTL extension requires an open folder or workspace to work correctly.')
return ''
}

return workspaceFolders[0].uri.fsPath
}

export const resolveFtlPath = async (workspaceRoot: string, config: vscode.WorkspaceConfiguration): Promise<string> => {
const ftlPath = config.get<string>("executablePath")
const ftlPath = config.get<string>('executablePath')
if (!ftlPath || ftlPath.trim() === '') {
const path = await lookpath('ftl')
if (path) {
return path
}
vscode.window.showErrorMessage("FTL executable not found in PATH.")
throw new Error("FTL executable not found in PATH.")
vscode.window.showErrorMessage('FTL executable not found in PATH.')
throw new Error('FTL executable not found in PATH.')
}

return path.isAbsolute(ftlPath) ? ftlPath : path.join(workspaceRoot || '', ftlPath)
Expand All @@ -53,7 +54,6 @@ export const checkMinimumVersion = (version: string, minimumVersion: string): bo
return semver.valid(cleanVersion) ? semver.gte(cleanVersion, minimumVersion) : false
}


export const isFTLRunning = async (ftlPath: string): Promise<boolean> => {
try {
await execAsync(`${ftlPath} ping`)
Expand Down
Loading

0 comments on commit 3608866

Please sign in to comment.