github.com/goplus/gop@v1.2.6/x/langserver/serve_dial.go (about) 1 /* 2 * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package langserver 18 19 import ( 20 "context" 21 "io" 22 "io/fs" 23 "log" 24 "os" 25 "os/exec" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/goplus/gop/x/jsonrpc2/stdio" 31 ) 32 33 func fatal(err error) { 34 log.Fatalln(err) 35 } 36 37 // ----------------------------------------------------------------------------- 38 39 // ServeAndDialConfig represents the configuration of ServeAndDial. 40 type ServeAndDialConfig struct { 41 // OnError is to customize how to process errors (optional). 42 // It should panic in any case. 43 OnError func(err error) 44 } 45 46 const ( 47 logPrefix = "serve-" 48 logSuffix = ".log" 49 ) 50 51 func logFileOf(gopDir string, pid int) string { 52 return gopDir + logPrefix + strconv.Itoa(pid) + logSuffix 53 } 54 55 func isLog(fname string) bool { 56 return strings.HasSuffix(fname, logSuffix) && strings.HasPrefix(fname, logPrefix) 57 } 58 59 func pidByName(fname string) int { 60 pidText := fname[len(logPrefix) : len(fname)-len(logSuffix)] 61 if ret, err := strconv.ParseInt(pidText, 10, 0); err == nil { 62 return int(ret) 63 } 64 return -1 65 } 66 67 func killByPid(pid int) { 68 if pid < 0 { 69 return 70 } 71 if proc, err := os.FindProcess(pid); err == nil { 72 proc.Kill() 73 } 74 } 75 76 func tooOld(d fs.DirEntry) bool { 77 if fi, e := d.Info(); e == nil { 78 lastHour := time.Now().Add(-time.Hour) 79 return fi.ModTime().Before(lastHour) 80 } 81 return false 82 } 83 84 // ServeAndDial executes a command as a LangServer, makes a new connection to it 85 // and returns a client of the LangServer based on the connection. 86 func ServeAndDial(conf *ServeAndDialConfig, gopCmd string, args ...string) Client { 87 if conf == nil { 88 conf = new(ServeAndDialConfig) 89 } 90 onErr := conf.OnError 91 if onErr == nil { 92 onErr = fatal 93 } 94 95 home, err := os.UserHomeDir() 96 if err != nil { 97 onErr(err) 98 } 99 gopDir := home + "/.gop/" 100 err = os.MkdirAll(gopDir, 0755) 101 if err != nil { 102 onErr(err) 103 } 104 105 // logFile is where the LangServer application log saves to. 106 // default is ~/.gop/serve-{pid}.log 107 logFile := logFileOf(gopDir, os.Getpid()) 108 109 // clean too old logfiles, and kill old LangServer processes 110 go func() { 111 if fis, e := os.ReadDir(gopDir); e == nil { 112 for _, fi := range fis { 113 if fi.IsDir() { 114 continue 115 } 116 if fname := fi.Name(); isLog(fname) && tooOld(fi) { 117 os.Remove(gopDir + fname) 118 killByPid(pidByName(fname)) 119 } 120 } 121 } 122 }() 123 124 in, w := io.Pipe() 125 r, out := io.Pipe() 126 127 var cmd *exec.Cmd 128 go func() { 129 defer r.Close() 130 defer w.Close() 131 132 f, err := os.Create(logFile) 133 if err != nil { 134 onErr(err) 135 } 136 defer f.Close() 137 138 cmd = exec.Command(gopCmd, args...) 139 cmd.Stdin = r 140 cmd.Stdout = w 141 cmd.Stderr = f 142 err = cmd.Start() 143 if err != nil { 144 onErr(err) 145 } 146 147 newLogFile := logFileOf(gopDir, cmd.Process.Pid) 148 os.Rename(logFile, newLogFile) 149 150 cmd.Wait() 151 }() 152 153 c, err := Open(context.Background(), stdio.Dialer(in, out), func() { 154 if cmd == nil { 155 return 156 } 157 if proc := cmd.Process; proc != nil { 158 log.Println("==> ServeAndDial: kill", proc.Pid, gopCmd, args) 159 proc.Kill() 160 } 161 }) 162 if err != nil { 163 onErr(err) 164 } 165 return c 166 } 167 168 // -----------------------------------------------------------------------------