拜托,我在 golang Web 應用程式上有一個問題。我不明白為什么會發生
這是http處理程式的代碼
func (h *HttpServer) GenerateMobileApp(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
pid := vars["id"]
// Check if the project exists : gRPC - to Project micro-services - ask to project-api msvc
// var project *models.Project
// if project, err := h.projectClient.IsProjectExists() isProjectExists(pid); err != nil {
var project *protos.ProjectReply
idRequest := &protos.IDRequest{
Id: pid,
}
project, err := h.projectClient.IsProjectExists(r.Context(), idRequest)
if err != nil {
h.JSON(w, http.StatusInternalServerError, fmt.Errorf("error when checking project existance"))
return
}
if project == nil {
h.JSON(w, http.StatusBadRequest, fmt.Errorf("project does not exists"))
return
}
log.Println("Grpc downloaded Project : \n\t", project)
// Save locally the downloaded project
if err := h.store.SaveDownloadedProject(project); err != nil {
h.JSON(w, http.StatusBadRequest, fmt.Errorf("error when saving the downloaded project"))
return
}
// Download the repository
if err := h.mobileAPP.CloneBoilerplate(project.Id); err != nil {
h.JSON(w, http.StatusBadRequest, fmt.Errorf("error when cloning boilerplate"))
return
}
log.Println("Project successfully clone ", h.mobileAPP)
if err := h.mobileAPP.Update(project); err != nil {
h.JSON(w, http.StatusBadRequest, fmt.Errorf("error when updating boilerplate"))
return
}
log.Println("TODO - Update the project not done yet")
var wg sync.WaitGroup
wg.Add(1)
go func() {
apkGeneratedPath, err := h.mobileAPP.GenerateAPK(project.Id)
if err != nil {
log.Println("error when generation APK")
h.JSON(w, http.StatusBadRequest, fmt.Errorf("error when generating APK"))
wg.Done()
return
}
log.Println("APK Generated correctly: ", apkGeneratedPath)
wg.Done()
}()
wg.Wait()
// Save in the DB the APK generated
h.JSON(w, http.StatusCreated, "link of the play store app")
}
這是代碼 h.mobileAPP.GenerateAPK(project.id)
func (app *MobileAPP) GenerateAPK(projectID string) (string, error) {
log.Println("[GENERATED_APK] Start : ", projectID)
generatedAppFolder := fmt.Sprint("app-", projectID)
generatedAppFolderPath := filepath.Join(CLONED_APP_FOLDER, generatedAppFolder)
os.Chdir(generatedAppFolderPath) // (filepath.Join(CLONED_APP_FOLDER, projectID))
log.Println("[GENERATED_APK] Changed Directory to : ", generatedAppFolderPath)
log.Println("[GENERATED_APK] Running : flutter build appbundle")
cmd := exec.Command("flutter", "build", "appbundle")
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
cmd.Start()
var wg sync.WaitGroup
wg.Add(1)
go func() {
oneByteStderr := make([]byte, 100)
for {
_, err := stderr.Read(oneByteStderr)
if err != nil {
log.Println("[GENERATED_APK][STDErr] stdout.Read error : ", err.Error())
break
}
r := bufio.NewReader(stderr)
line, _, _ := r.ReadLine()
log.Println("[GENERATED_APK][STDErr] r.ReadLine() ", string(line))
}
wg.Done()
}()
// num := 1
oneByte := make([]byte, 100)
for {
_, err := stdout.Read(oneByte)
if err != nil {
log.Println("[GENERATED_APK] stdout.Read error : ", err.Error())
break
}
r := bufio.NewReader(stdout)
line, _, _ := r.ReadLine()
log.Println("[GENERATED_APK] r.ReadLine() ", string(line))
}
wg.Wait()
err := cmd.Wait()
if err != nil {
log.Fatalf("[GENERATED_APK] cmd.Run() failed with %s\n", err)
}
return "", nil
// // Copy the APK, Rename it
// // Development: app-generated file
// // Production: AWS S3
// apkGeneratedPath := filepath.Join(generatedAppFolderPath, "build/app/outputs/apk/release/app-release.apk")
// apkBackupFolder := filepath.Join(APK_BACKUP_FOLDER, projectID)
// // Check if the backup folder already exists
// log.Println("[GENERATED_APK] Check if the backup folder already exists : ", apkBackupFolder)
// if _, err := os.Stat(apkBackupFolder); os.IsNotExist(err) {
// log.Println("[GENERATED_APK] Backup Folder does not exists. Create it")
// os.MkdirAll(apkBackupFolder, os.ModePerm)
// log.Println("[GENERATED_APK] Backup Folder created: ", apkBackupFolder)
// }
// // Move the APK to the backup folder
// // _, err = copyFile(apkFileGeneratedPath, apkBackupFolder)
// apkGeneratedFinalName := fmt.Sprintf("apk-%s__%s", projectID, time.Now().Format("13-02-2006_15:04:05"))
// apkGeneratedFinalPath := filepath.Join(apkBackupFolder, apkGeneratedFinalName)
// err = os.Rename(apkGeneratedPath, apkGeneratedFinalPath)
// if err != nil {
// log.Println("Error while copying : ", apkGeneratedPath)
// return "", err
// }
// log.Println("[GENERATED_APK] Ends")
// return apkGeneratedFinalPath, nil
}
您會注意到我正在運行命令 flutter build appbundle cmd := exec.Command("flutter", "build", "appbundle")
我遇到的問題是該命令沒有完成。在執行期間,幾分鐘后 http 處理程式從頭重新啟動。不是GeneratedAPK方法,而是整個處理程式func (h *HttpServer) GenerateMobileApp從頭開始重新啟動,從而導致許多顫振構建 appbundle 行程。當第一個完成時它就停止了。它使 http 處理程式太長。
而在對應pod的日志中,我們有
[K8s EVENT: Pod generator-api-6d5b979bb7-wmw7n (ns: default)] Back-off restarting failed container
[K8s EVENT: Pod generator-api-6d5b979bb7-wmw7n (ns: default)] Container image "guitou-app/msvc-generator-api:tilt-3c8bcf3c86172c1e" already present on machine
Detected container restart. Pod: generator-api-6d5b979bb7-wmw7n. Container: generator-api.
如何檢測正在發生的事情?請問,怎么解決?
uj5u.com熱心網友回復:
有幾點需要注意:
- 捕獲
stdout并且stderr看起來很脆弱(來自 goroutine 的錯誤處理不會正確傳播) - 長時間運行的可執行檔案可能會導致請求超時問題
捕獲stdout和stderr- 我建議使用類似exec.Cmd.CombinedOutput例如
cmd := exec.CommandContext(ctx, "flutter", "build", "appbundle") // see below for ctx details
var sout, serr bytes.Buffer
c.Stdout = &sout
c.Stderr = &serr
err := c.Run()
if err != nil { /* ... */ }
outBody, errBody := string(sout.Bytes()), string(serr.Bytes())
所以不需要復雜的等待組或 goroutine 之間的復雜錯誤處理。
您需要處理網路請求可能需要很長時間的情況。如果請求被取消(由客戶端或服務器) - 您需要一種機制來取消請求的子任務(例如,您在外部執行的流程)
在運行任何 Web 服務時,請始終確保利用context.Context請求的生命周期——尤其是在運行任何潛在的阻塞操作時(就像您使用的那樣exec.Command)。
因此,首先,從您的處理程式中獲取請求context.Context,并將其傳遞給任何潛在的阻塞呼叫:
func (h *HttpServer) GenerateMobileApp(w http.ResponseWriter, r *http.Request) {
...
ctx := r.Context() // context.Context for the lifetime of the request
...
go func() {
apkGeneratedPath, err := h.mobileAPP.GenerateAPK(ctx, project.Id) // <- add ctx
}
}
并像這樣更新您的 APK 生成器簽名:
func (app *MobileAPP) GenerateAPK(ctx context.Context, projectID string) (string, error)
并ctx在執行外部工具時使用:
//cmd := exec.Command("flutter", "build", "appbundle")
cmd := exec.CommandContext(ctx, "flutter", "build", "appbundle")
有了這個,如果 Web 請求被取消(超時 - 基于 Web 服務器設定;或客戶端斷開連接),該程序將被終止。您甚至可以通過檢查(客戶端斷開連接)或(請求超時)的錯誤來exec.CommandContext為這種情況設定特殊的清理邏輯。context.Canceledcontext.DeadlineExceeded
uj5u.com熱心網友回復:
要確定程式掛起的位置,請添加到您的代碼中:
func dumpStackAfter(interval time.Duration) {
go func() {
time.Sleep(interval)
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic("timeouted")
}()
}
并在 main 函式的開頭呼叫它:
dumpStackAfter(time.Second*30)
運行你的程式并在 30 秒內列印堆疊跟蹤,顯示程式掛起的位置。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/414940.html
標籤:
