Home > Net >  Go Http Handler is restarting itself during the execution of a command (cmd.Command)
Go Http Handler is restarting itself during the execution of a command (cmd.Command)

Time:01-19

Please, I have an issue on a golang web application. I don’t understand why is happening

Here is the code of the http handler

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")
}

Here is the code of 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
}

You can notice that I am running a command flutter build appbundle at cmd := exec.Command("flutter", "build", "appbundle")

The issue I have is that that command doesn’t finish. During it’s execution, after a few minute the http handler restart from the beginning. Not the GeneratedAPK method but the whole handler func (h *HttpServer) GenerateMobileApp restart from the beginning thus causing many flutter build appbundle processes . And it stopped when the first one is done. And it makes the http handler too long.

And in the logs of the corresponding pod, we have

[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.

How can I detect what is happening? Please, How to solve it?

CodePudding user response:

What is probably happening in your case is, the web-request is taking too long, and the request is canceled - but you have no mechanism in place to cancel your externally executed process(es). So...

When running any webservice always ensure to leverage context.Context for the lifetime of a request - especially when running any potentially blocking operations (as you are with exec.Command).

So first, from your handler, grab the request's context.Context, and pass this along to any potentially blocking calls:

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
    }

}

and update your APK generator signature like so:

func (app *MobileAPP) GenerateAPK(ctx context.Context, projectID string) (string, error)

and to use ctx when executing an external tool:

//cmd := exec.Command("flutter", "build", "appbundle")
cmd := exec.CommandContext(ctx, "flutter", "build", "appbundle")

with this in place, if the web request is canceled (times out - based on web server settings; or the client disconnects) the process will be terminated. You can even have special clean-up logic in place for this occurrence, by checking the error from exec.CommandContext for either context.Canceled (client disconnected) or context.DeadlineExceeded (request timed-out).

CodePudding user response:

To determine the place where the program hangs up add to your code:

func dumpStackAfter(interval time.Duration) {
        go func() {
                time.Sleep(interval)
                pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
                panic("timeouted")
        }()
}

And call it at the beginning of main function:

dumpStackAfter(time.Second*30)

Run you program and in 30 sec it will print stack trace which will show where the program hanged up.

  •  Tags:  
  • Related