internal/cmd/serve_command.go (141 lines of code) (raw):
package cmd
import (
"context"
"flag"
"fmt"
"log"
"os"
"runtime"
"runtime/pprof"
"strings"
"syscall"
lsctx "github.com/Azure/azapi-lsp/internal/context"
"github.com/Azure/azapi-lsp/internal/langserver"
"github.com/Azure/azapi-lsp/internal/langserver/handlers"
"github.com/Azure/azapi-lsp/internal/logging"
"github.com/Azure/azapi-lsp/internal/pathtpl"
"github.com/mitchellh/cli"
)
type ServeCommand struct {
Ui cli.Ui
Version string
// flags
port int
logFilePath string
cpuProfile string
memProfile string
reqConcurrency int
}
func (c *ServeCommand) flags() *flag.FlagSet {
fs := defaultFlagSet("serve")
fs.IntVar(&c.port, "port", 0, "port number to listen on (turns server into TCP mode)")
fs.StringVar(&c.logFilePath, "log-file", "", "path to a file to log into with support "+
"for variables (e.g. Timestamp, Pid, Ppid) via Go template syntax {{.VarName}}")
fs.StringVar(&c.cpuProfile, "cpuprofile", "", "file into which to write CPU profile (if not empty)"+
" with support for variables (e.g. Timestamp, Pid, Ppid) via Go template"+
" syntax {{.VarName}}")
fs.StringVar(&c.memProfile, "memprofile", "", "file into which to write memory profile (if not empty)"+
" with support for variables (e.g. Timestamp, Pid, Ppid) via Go template"+
" syntax {{.VarName}}")
fs.IntVar(&c.reqConcurrency, "req-concurrency", 0, fmt.Sprintf("number of RPC requests to process concurrently,"+
" defaults to %d, concurrency lower than 2 is not recommended", langserver.DefaultConcurrency()))
fs.Usage = func() { c.Ui.Error(c.Help()) }
return fs
}
func (c *ServeCommand) Run(args []string) int {
// #nosec G304
f := c.flags()
if err := f.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s", err))
return 1
}
if c.cpuProfile != "" {
stop, err := writeCpuProfileInto(c.cpuProfile)
defer stop() //nolint
if err != nil {
c.Ui.Error(err.Error())
return 1
}
}
if c.memProfile != "" {
defer writeMemoryProfileInto(c.memProfile) //nolint
}
var logger *log.Logger
if c.logFilePath != "" {
fl, err := logging.NewFileLogger(c.logFilePath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to setup file logging: %s", err))
return 1
}
defer fl.Close()
logger = fl.Logger()
} else {
logger = logging.NewLogger(os.Stderr)
}
ctx, cancelFunc := lsctx.WithSignalCancel(context.Background(), logger,
syscall.SIGINT, syscall.SIGTERM)
defer cancelFunc()
if c.reqConcurrency != 0 {
ctx = langserver.WithRequestConcurrency(ctx, c.reqConcurrency)
logger.Printf("Custom request concurrency set to %d", c.reqConcurrency)
}
logger.Printf("Starting azapi-lsp %s", c.Version)
ctx = lsctx.WithLanguageServerVersion(ctx, c.Version)
srv := langserver.NewLangServer(ctx, handlers.NewSession)
srv.SetLogger(logger)
if c.port != 0 {
err := srv.StartTCP(fmt.Sprintf("localhost:%d", c.port))
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to start TCP server: %s", err))
return 1
}
return 0
}
err := srv.StartAndWait(os.Stdin, os.Stdout)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to start server: %s", err))
return 1
}
return 0
}
type stopFunc func() error
func writeCpuProfileInto(rawPath string) (stopFunc, error) {
path, err := pathtpl.ParseRawPath("cpuprofile-path", rawPath)
if err != nil {
return nil, err
}
// #nosec G304
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("could not create CPU profile: %s", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
return f.Close, fmt.Errorf("could not start CPU profile: %s", err)
}
return func() error {
pprof.StopCPUProfile()
return f.Close()
}, nil
}
func writeMemoryProfileInto(rawPath string) error {
path, err := pathtpl.ParseRawPath("memprofile-path", rawPath)
if err != nil {
return err
}
// #nosec G304
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("could not create memory profile: %s", err)
}
// #nosec G307
defer f.Close()
runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil {
return fmt.Errorf("could not write memory profile: %s", err)
}
return nil
}
func (c *ServeCommand) Help() string {
helpText := `
Usage: azapi-lsp serve [options]
` + c.Synopsis() + "\n\n" + helpForFlags(c.flags())
return strings.TrimSpace(helpText)
}
func (c *ServeCommand) Synopsis() string {
return "Starts the Language Server"
}