e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build windows
|
|
// +build windows
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/alibaba/opensandbox/execd/pkg/jupyter/execute"
|
|
"github.com/alibaba/opensandbox/execd/pkg/log"
|
|
"github.com/alibaba/opensandbox/execd/pkg/util/pathutil"
|
|
"github.com/alibaba/opensandbox/internal/safego"
|
|
)
|
|
|
|
// runCommand executes shell commands and streams their output on Windows.
|
|
func (c *Controller) runCommand(ctx context.Context, request *ExecuteCodeRequest) error {
|
|
session := c.newContextID()
|
|
request.Hooks.OnExecuteInit(session)
|
|
|
|
stdout, stderr, err := c.stdLogDescriptor(session)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get stdlog descriptor: %w", err)
|
|
}
|
|
|
|
startAt := time.Now()
|
|
log.Info("received command: %v", log.SanitizeCommand(request.Code))
|
|
cmd := exec.CommandContext(ctx, "cmd", "/C", request.Code)
|
|
extraEnv := mergeExtraEnvs(loadExtraEnvFromFile(), request.Envs)
|
|
cwd, err := pathutil.ExpandPathWithEnv(request.Cwd, extraEnv)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve cwd: %w", err)
|
|
}
|
|
|
|
cmd.Stdout = stdout
|
|
cmd.Stderr = stderr
|
|
cmd.Dir = cwd
|
|
cmd.Env = mergeEnvs(os.Environ(), extraEnv)
|
|
|
|
done := make(chan struct{}, 1)
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
safego.Go(func() {
|
|
defer wg.Done()
|
|
c.tailStdPipe(c.stdoutFileName(session), request.Hooks.OnExecuteStdout, done)
|
|
})
|
|
safego.Go(func() {
|
|
defer wg.Done()
|
|
c.tailStdPipe(c.stderrFileName(session), request.Hooks.OnExecuteStderr, done)
|
|
})
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
close(done)
|
|
wg.Wait()
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{EName: "CommandExecError", EValue: err.Error()})
|
|
log.Error("CommandExecError: error starting commands: %v", err)
|
|
return nil
|
|
}
|
|
|
|
kernel := &commandKernel{
|
|
pid: cmd.Process.Pid,
|
|
content: request.Code,
|
|
isBackground: false,
|
|
}
|
|
c.storeCommandKernel(session, kernel)
|
|
|
|
err = cmd.Wait()
|
|
close(done)
|
|
wg.Wait()
|
|
if err != nil {
|
|
var eName, eValue string
|
|
var traceback []string
|
|
|
|
var exitError *exec.ExitError
|
|
if errors.As(err, &exitError) {
|
|
exitCode := exitError.ExitCode()
|
|
eName = "CommandExecError"
|
|
eValue = strconv.Itoa(exitCode)
|
|
} else {
|
|
eName = "CommandExecError"
|
|
eValue = err.Error()
|
|
}
|
|
traceback = []string{err.Error()}
|
|
|
|
request.Hooks.OnExecuteError(&execute.ErrorOutput{
|
|
EName: eName,
|
|
EValue: eValue,
|
|
Traceback: traceback,
|
|
})
|
|
|
|
log.Error("CommandExecError: error running commands: %v", err)
|
|
return nil
|
|
}
|
|
request.Hooks.OnExecuteComplete(time.Since(startAt))
|
|
return nil
|
|
}
|
|
|
|
// runBackgroundCommand executes shell commands in detached mode on Windows.
|
|
func (c *Controller) runBackgroundCommand(ctx context.Context, cancel context.CancelFunc, request *ExecuteCodeRequest) error {
|
|
session := c.newContextID()
|
|
request.Hooks.OnExecuteInit(session)
|
|
|
|
pipe, err := c.combinedOutputDescriptor(session)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get combined output descriptor: %w", err)
|
|
}
|
|
stdoutPath := c.combinedOutputFileName(session)
|
|
stderrPath := c.combinedOutputFileName(session)
|
|
|
|
startAt := time.Now()
|
|
log.Info("received command: %v", log.SanitizeCommand(request.Code))
|
|
cmd := exec.CommandContext(ctx, "cmd", "/C", request.Code)
|
|
extraEnv := mergeExtraEnvs(loadExtraEnvFromFile(), request.Envs)
|
|
cwd, err := pathutil.ExpandPathWithEnv(request.Cwd, extraEnv)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve cwd: %w", err)
|
|
}
|
|
|
|
cmd.Dir = cwd
|
|
cmd.Stdout = pipe
|
|
cmd.Stderr = pipe
|
|
cmd.Env = mergeEnvs(os.Environ(), extraEnv)
|
|
|
|
devNull, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0) // best-effort, ignore error
|
|
cmd.Stdin = devNull
|
|
|
|
safego.Go(func() {
|
|
err := cmd.Start()
|
|
if err != nil {
|
|
log.Error("CommandExecError: error starting commands: %v", err)
|
|
pipe.Close() // best-effort
|
|
cancel()
|
|
return
|
|
}
|
|
|
|
kernel := &commandKernel{
|
|
pid: cmd.Process.Pid,
|
|
content: request.Code,
|
|
stdoutPath: stdoutPath,
|
|
stderrPath: stderrPath,
|
|
startedAt: startAt,
|
|
running: true,
|
|
isBackground: true,
|
|
}
|
|
c.storeCommandKernel(session, kernel)
|
|
|
|
safego.Go(func() {
|
|
<-ctx.Done()
|
|
if cmd.Process != nil {
|
|
_ = cmd.Process.Kill() // best-effort
|
|
}
|
|
})
|
|
|
|
err = cmd.Wait()
|
|
cancel()
|
|
pipe.Close() // best-effort
|
|
devNull.Close() // best-effort
|
|
|
|
if err != nil {
|
|
log.Error("CommandExecError: error running commands: %v", err)
|
|
exitCode := 1
|
|
var exitError *exec.ExitError
|
|
if errors.As(err, &exitError) {
|
|
exitCode = exitError.ExitCode()
|
|
}
|
|
c.markCommandFinished(session, exitCode, err.Error())
|
|
return
|
|
}
|
|
c.markCommandFinished(session, 0, "")
|
|
})
|
|
|
|
request.Hooks.OnExecuteComplete(time.Since(startAt))
|
|
return nil
|
|
}
|