diff options
Diffstat (limited to 'vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go')
-rw-r--r-- | vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go | 658 |
1 files changed, 330 insertions, 328 deletions
diff --git a/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go b/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go index 20b242524..98df25bd5 100644 --- a/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go +++ b/vendor/github.com/Microsoft/hcsshim/internal/hcs/system.go @@ -1,18 +1,24 @@ package hcs import ( + "context" "encoding/json" + "errors" "os" "strconv" + "strings" "sync" "syscall" "time" - "github.com/Microsoft/hcsshim/internal/interop" - "github.com/Microsoft/hcsshim/internal/logfields" + "github.com/Microsoft/hcsshim/internal/cow" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/schema1" + hcsschema "github.com/Microsoft/hcsshim/internal/schema2" "github.com/Microsoft/hcsshim/internal/timeout" - "github.com/sirupsen/logrus" + "github.com/Microsoft/hcsshim/internal/vmcompute" + "go.opencensus.io/trace" ) // currentContainerStarts is used to limit the number of concurrent container @@ -38,49 +44,37 @@ func init() { type System struct { handleLock sync.RWMutex - handle hcsSystem + handle vmcompute.HcsSystem id string callbackNumber uintptr - logctx logrus.Fields + closedWaitOnce sync.Once + waitBlock chan struct{} + waitError error + exitError error + + os, typ string } func newSystem(id string) *System { return &System{ - id: id, - logctx: logrus.Fields{ - logfields.ContainerID: id, - }, - } -} - -func (computeSystem *System) logOperationBegin(operation string) { - logOperationBegin( - computeSystem.logctx, - operation+" - Begin Operation") -} - -func (computeSystem *System) logOperationEnd(operation string, err error) { - var result string - if err == nil { - result = "Success" - } else { - result = "Error" + id: id, + waitBlock: make(chan struct{}), } - - logOperationEnd( - computeSystem.logctx, - operation+" - End Operation - "+result, - err) } // CreateComputeSystem creates a new compute system with the given configuration but does not start it. -func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (_ *System, err error) { +func CreateComputeSystem(ctx context.Context, id string, hcsDocumentInterface interface{}) (_ *System, err error) { operation := "hcsshim::CreateComputeSystem" + // hcsCreateComputeSystemContext is an async operation. Start the outer span + // here to measure the full create time. + ctx, span := trace.StartSpan(ctx, operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", id)) + computeSystem := newSystem(id) - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() hcsDocumentB, err := json.Marshal(hcsDocumentInterface) if err != nil { @@ -89,126 +83,114 @@ func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (_ *System hcsDocument := string(hcsDocumentB) - logrus.WithFields(computeSystem.logctx). - WithField(logfields.JSON, hcsDocument). - Debug("HCS ComputeSystem Document") - var ( - resultp *uint16 identity syscall.Handle + resultJSON string createError error ) - syscallWatcher(computeSystem.logctx, func() { - createError = hcsCreateComputeSystem(id, hcsDocument, identity, &computeSystem.handle, &resultp) - }) - + computeSystem.handle, resultJSON, createError = vmcompute.HcsCreateComputeSystem(ctx, id, hcsDocument, identity) if createError == nil || IsPending(createError) { - if err = computeSystem.registerCallback(); err != nil { + defer func() { + if err != nil { + computeSystem.Close() + } + }() + if err = computeSystem.registerCallback(ctx); err != nil { // Terminate the compute system if it still exists. We're okay to // ignore a failure here. - computeSystem.Terminate() + computeSystem.Terminate(ctx) return nil, makeSystemError(computeSystem, operation, "", err, nil) } } - events, err := processAsyncHcsResult(createError, resultp, computeSystem.callbackNumber, hcsNotificationSystemCreateCompleted, &timeout.SystemCreate) + events, err := processAsyncHcsResult(ctx, createError, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemCreateCompleted, &timeout.SystemCreate) if err != nil { if err == ErrTimeout { // Terminate the compute system if it still exists. We're okay to // ignore a failure here. - computeSystem.Terminate() + computeSystem.Terminate(ctx) } return nil, makeSystemError(computeSystem, operation, hcsDocument, err, events) } - + go computeSystem.waitBackground() + if err = computeSystem.getCachedProperties(ctx); err != nil { + return nil, err + } return computeSystem, nil } // OpenComputeSystem opens an existing compute system by ID. -func OpenComputeSystem(id string) (_ *System, err error) { +func OpenComputeSystem(ctx context.Context, id string) (*System, error) { operation := "hcsshim::OpenComputeSystem" computeSystem := newSystem(id) - computeSystem.logOperationBegin(operation) + handle, resultJSON, err := vmcompute.HcsOpenComputeSystem(ctx, id) + events := processHcsResult(ctx, resultJSON) + if err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, events) + } + computeSystem.handle = handle defer func() { - if IsNotExist(err) { - computeSystem.logOperationEnd(operation, nil) - } else { - computeSystem.logOperationEnd(operation, err) + if err != nil { + computeSystem.Close() } }() + if err = computeSystem.registerCallback(ctx); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } + go computeSystem.waitBackground() + if err = computeSystem.getCachedProperties(ctx); err != nil { + return nil, err + } + return computeSystem, nil +} - var ( - handle hcsSystem - resultp *uint16 - ) - err = hcsOpenComputeSystem(id, &handle, &resultp) - events := processHcsResult(resultp) +func (computeSystem *System) getCachedProperties(ctx context.Context) error { + props, err := computeSystem.Properties(ctx) if err != nil { - return nil, makeSystemError(computeSystem, operation, "", err, events) + return err } - - computeSystem.handle = handle - - if err = computeSystem.registerCallback(); err != nil { - return nil, makeSystemError(computeSystem, operation, "", err, nil) + computeSystem.typ = strings.ToLower(props.SystemType) + computeSystem.os = strings.ToLower(props.RuntimeOSType) + if computeSystem.os == "" && computeSystem.typ == "container" { + // Pre-RS5 HCS did not return the OS, but it only supported containers + // that ran Windows. + computeSystem.os = "windows" } + return nil +} - return computeSystem, nil +// OS returns the operating system of the compute system, "linux" or "windows". +func (computeSystem *System) OS() string { + return computeSystem.os +} + +// IsOCI returns whether processes in the compute system should be created via +// OCI. +func (computeSystem *System) IsOCI() bool { + return computeSystem.os == "linux" && computeSystem.typ == "container" } // GetComputeSystems gets a list of the compute systems on the system that match the query -func GetComputeSystems(q schema1.ComputeSystemQuery) (_ []schema1.ContainerProperties, err error) { +func GetComputeSystems(ctx context.Context, q schema1.ComputeSystemQuery) ([]schema1.ContainerProperties, error) { operation := "hcsshim::GetComputeSystems" - fields := logrus.Fields{} - logOperationBegin( - fields, - operation+" - Begin Operation") - - defer func() { - var result string - if err == nil { - result = "Success" - } else { - result = "Error" - } - - logOperationEnd( - fields, - operation+" - End Operation - "+result, - err) - }() queryb, err := json.Marshal(q) if err != nil { return nil, err } - query := string(queryb) - - logrus.WithFields(fields). - WithField(logfields.JSON, query). - Debug("HCS ComputeSystem Query") - - var ( - resultp *uint16 - computeSystemsp *uint16 - ) - - syscallWatcher(fields, func() { - err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp) - }) - events := processHcsResult(resultp) + computeSystemsJSON, resultJSON, err := vmcompute.HcsEnumerateComputeSystems(ctx, string(queryb)) + events := processHcsResult(ctx, resultJSON) if err != nil { return nil, &HcsError{Op: operation, Err: err, Events: events} } - if computeSystemsp == nil { + if computeSystemsJSON == "" { return nil, ErrUnexpectedValue } - computeSystemsRaw := interop.ConvertAndFreeCoTaskMemBytes(computeSystemsp) computeSystems := []schema1.ContainerProperties{} - if err = json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil { + if err = json.Unmarshal([]byte(computeSystemsJSON), &computeSystems); err != nil { return nil, err } @@ -216,16 +198,21 @@ func GetComputeSystems(q schema1.ComputeSystemQuery) (_ []schema1.ContainerPrope } // Start synchronously starts the computeSystem. -func (computeSystem *System) Start() (err error) { +func (computeSystem *System) Start(ctx context.Context) (err error) { + operation := "hcsshim::System::Start" + + // hcsStartComputeSystemContext is an async operation. Start the outer span + // here to measure the full start time. + ctx, span := trace.StartSpan(ctx, operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Start" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Start", "", ErrAlreadyClosed, nil) + return makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } // This is a very simple backoff-retry loop to limit the number @@ -254,13 +241,10 @@ func (computeSystem *System) Start() (err error) { }() } - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsStartComputeSystem(computeSystem.handle, "", &resultp) - }) - events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemStartCompleted, &timeout.SystemStart) + resultJSON, err := vmcompute.HcsStartComputeSystem(ctx, computeSystem.handle, "") + events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemStartCompleted, &timeout.SystemStart) if err != nil { - return makeSystemError(computeSystem, "Start", "", err, events) + return makeSystemError(computeSystem, operation, "", err, events) } return nil @@ -271,360 +255,388 @@ func (computeSystem *System) ID() string { return computeSystem.id } -// Shutdown requests a compute system shutdown, if IsPending() on the error returned is true, -// it may not actually be shut down until Wait() succeeds. -func (computeSystem *System) Shutdown() (err error) { +// Shutdown requests a compute system shutdown. +func (computeSystem *System) Shutdown(ctx context.Context) error { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Shutdown" - computeSystem.logOperationBegin(operation) - defer func() { - if IsAlreadyStopped(err) { - computeSystem.logOperationEnd(operation, nil) - } else { - computeSystem.logOperationEnd(operation, err) - } - }() + operation := "hcsshim::System::Shutdown" if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Shutdown", "", ErrAlreadyClosed, nil) + return nil } - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsShutdownComputeSystem(computeSystem.handle, "", &resultp) - }) - events := processHcsResult(resultp) - if err != nil { - return makeSystemError(computeSystem, "Shutdown", "", err, events) + resultJSON, err := vmcompute.HcsShutdownComputeSystem(ctx, computeSystem.handle, "") + events := processHcsResult(ctx, resultJSON) + switch err { + case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending: + default: + return makeSystemError(computeSystem, operation, "", err, events) } - return nil } -// Terminate requests a compute system terminate, if IsPending() on the error returned is true, -// it may not actually be shut down until Wait() succeeds. -func (computeSystem *System) Terminate() (err error) { +// Terminate requests a compute system terminate. +func (computeSystem *System) Terminate(ctx context.Context) error { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Terminate" - computeSystem.logOperationBegin(operation) - defer func() { - if IsPending(err) { - computeSystem.logOperationEnd(operation, nil) - } else { - computeSystem.logOperationEnd(operation, err) - } - }() + operation := "hcsshim::System::Terminate" if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Terminate", "", ErrAlreadyClosed, nil) + return nil } - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsTerminateComputeSystem(computeSystem.handle, "", &resultp) - }) - events := processHcsResult(resultp) - if err != nil && err != ErrVmcomputeAlreadyStopped { - return makeSystemError(computeSystem, "Terminate", "", err, events) + resultJSON, err := vmcompute.HcsTerminateComputeSystem(ctx, computeSystem.handle, "") + events := processHcsResult(ctx, resultJSON) + switch err { + case nil, ErrVmcomputeAlreadyStopped, ErrComputeSystemDoesNotExist, ErrVmcomputeOperationPending: + default: + return makeSystemError(computeSystem, operation, "", err, events) } - return nil } -// Wait synchronously waits for the compute system to shutdown or terminate. -func (computeSystem *System) Wait() (err error) { - operation := "hcsshim::ComputeSystem::Wait" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - - err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil) - if err != nil { - return makeSystemError(computeSystem, "Wait", "", err, nil) - } - - return nil +// waitBackground waits for the compute system exit notification. Once received +// sets `computeSystem.waitError` (if any) and unblocks all `Wait` calls. +// +// This MUST be called exactly once per `computeSystem.handle` but `Wait` is +// safe to call multiple times. +func (computeSystem *System) waitBackground() { + operation := "hcsshim::System::waitBackground" + ctx, span := trace.StartSpan(context.Background(), operation) + defer span.End() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + + err := waitForNotification(ctx, computeSystem.callbackNumber, hcsNotificationSystemExited, nil) + switch err { + case nil: + log.G(ctx).Debug("system exited") + case ErrVmcomputeUnexpectedExit: + log.G(ctx).Debug("unexpected system exit") + computeSystem.exitError = makeSystemError(computeSystem, operation, "", err, nil) + err = nil + default: + err = makeSystemError(computeSystem, operation, "", err, nil) + } + computeSystem.closedWaitOnce.Do(func() { + computeSystem.waitError = err + close(computeSystem.waitBlock) + }) + oc.SetSpanStatus(span, err) } -// WaitExpectedError synchronously waits for the compute system to shutdown or -// terminate, and ignores the passed error if it occurs. -func (computeSystem *System) WaitExpectedError(expected error) (err error) { - operation := "hcsshim::ComputeSystem::WaitExpectedError" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() +// Wait synchronously waits for the compute system to shutdown or terminate. If +// the compute system has already exited returns the previous error (if any). +func (computeSystem *System) Wait() error { + <-computeSystem.waitBlock + return computeSystem.waitError +} - err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil) - if err != nil && getInnerError(err) != expected { - return makeSystemError(computeSystem, "WaitExpectedError", "", err, nil) +// ExitError returns an error describing the reason the compute system terminated. +func (computeSystem *System) ExitError() error { + select { + case <-computeSystem.waitBlock: + if computeSystem.waitError != nil { + return computeSystem.waitError + } + return computeSystem.exitError + default: + return errors.New("container not exited") } - - return nil } -// WaitTimeout synchronously waits for the compute system to terminate or the duration to elapse. -// If the timeout expires, IsTimeout(err) == true -func (computeSystem *System) WaitTimeout(timeout time.Duration) (err error) { - operation := "hcsshim::ComputeSystem::WaitTimeout" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() +// Properties returns the requested container properties targeting a V1 schema container. +func (computeSystem *System) Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error) { + computeSystem.handleLock.RLock() + defer computeSystem.handleLock.RUnlock() + + operation := "hcsshim::System::Properties" + + queryBytes, err := json.Marshal(schema1.PropertyQuery{PropertyTypes: types}) + if err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } - err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, &timeout) + propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes)) + events := processHcsResult(ctx, resultJSON) if err != nil { - return makeSystemError(computeSystem, "WaitTimeout", "", err, nil) + return nil, makeSystemError(computeSystem, operation, "", err, events) } - return nil + if propertiesJSON == "" { + return nil, ErrUnexpectedValue + } + properties := &schema1.ContainerProperties{} + if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } + + return properties, nil } -func (computeSystem *System) Properties(types ...schema1.PropertyType) (_ *schema1.ContainerProperties, err error) { +// PropertiesV2 returns the requested container properties targeting a V2 schema container. +func (computeSystem *System) PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Properties" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() + operation := "hcsshim::System::PropertiesV2" - queryj, err := json.Marshal(schema1.PropertyQuery{types}) + queryBytes, err := json.Marshal(hcsschema.PropertyQuery{PropertyTypes: types}) if err != nil { - return nil, makeSystemError(computeSystem, "Properties", "", err, nil) + return nil, makeSystemError(computeSystem, operation, "", err, nil) } - logrus.WithFields(computeSystem.logctx). - WithField(logfields.JSON, queryj). - Debug("HCS ComputeSystem Properties Query") - - var resultp, propertiesp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsGetComputeSystemProperties(computeSystem.handle, string(queryj), &propertiesp, &resultp) - }) - events := processHcsResult(resultp) + propertiesJSON, resultJSON, err := vmcompute.HcsGetComputeSystemProperties(ctx, computeSystem.handle, string(queryBytes)) + events := processHcsResult(ctx, resultJSON) if err != nil { - return nil, makeSystemError(computeSystem, "Properties", "", err, events) + return nil, makeSystemError(computeSystem, operation, "", err, events) } - if propertiesp == nil { + if propertiesJSON == "" { return nil, ErrUnexpectedValue } - propertiesRaw := interop.ConvertAndFreeCoTaskMemBytes(propertiesp) - properties := &schema1.ContainerProperties{} - if err := json.Unmarshal(propertiesRaw, properties); err != nil { - return nil, makeSystemError(computeSystem, "Properties", "", err, nil) + properties := &hcsschema.Properties{} + if err := json.Unmarshal([]byte(propertiesJSON), properties); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) } return properties, nil } // Pause pauses the execution of the computeSystem. This feature is not enabled in TP5. -func (computeSystem *System) Pause() (err error) { +func (computeSystem *System) Pause(ctx context.Context) (err error) { + operation := "hcsshim::System::Pause" + + // hcsPauseComputeSystemContext is an async peration. Start the outer span + // here to measure the full pause time. + ctx, span := trace.StartSpan(ctx, operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Pause" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Pause", "", ErrAlreadyClosed, nil) + return makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsPauseComputeSystem(computeSystem.handle, "", &resultp) - }) - events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemPauseCompleted, &timeout.SystemPause) + resultJSON, err := vmcompute.HcsPauseComputeSystem(ctx, computeSystem.handle, "") + events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemPauseCompleted, &timeout.SystemPause) if err != nil { - return makeSystemError(computeSystem, "Pause", "", err, events) + return makeSystemError(computeSystem, operation, "", err, events) } return nil } // Resume resumes the execution of the computeSystem. This feature is not enabled in TP5. -func (computeSystem *System) Resume() (err error) { +func (computeSystem *System) Resume(ctx context.Context) (err error) { + operation := "hcsshim::System::Resume" + + // hcsResumeComputeSystemContext is an async operation. Start the outer span + // here to measure the full restore time. + ctx, span := trace.StartSpan(ctx, operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Resume" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Resume", "", ErrAlreadyClosed, nil) + return makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsResumeComputeSystem(computeSystem.handle, "", &resultp) - }) - events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemResumeCompleted, &timeout.SystemResume) + resultJSON, err := vmcompute.HcsResumeComputeSystem(ctx, computeSystem.handle, "") + events, err := processAsyncHcsResult(ctx, err, resultJSON, computeSystem.callbackNumber, hcsNotificationSystemResumeCompleted, &timeout.SystemResume) if err != nil { - return makeSystemError(computeSystem, "Resume", "", err, events) + return makeSystemError(computeSystem, operation, "", err, events) } return nil } -// CreateProcess launches a new process within the computeSystem. -func (computeSystem *System) CreateProcess(c interface{}) (_ *Process, err error) { +func (computeSystem *System) createProcess(ctx context.Context, operation string, c interface{}) (*Process, *vmcompute.HcsProcessInformation, error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::CreateProcess" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - - var ( - processInfo hcsProcessInformation - processHandle hcsProcess - resultp *uint16 - ) - if computeSystem.handle == 0 { - return nil, makeSystemError(computeSystem, "CreateProcess", "", ErrAlreadyClosed, nil) + return nil, nil, makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } configurationb, err := json.Marshal(c) if err != nil { - return nil, makeSystemError(computeSystem, "CreateProcess", "", err, nil) + return nil, nil, makeSystemError(computeSystem, operation, "", err, nil) } configuration := string(configurationb) + processInfo, processHandle, resultJSON, err := vmcompute.HcsCreateProcess(ctx, computeSystem.handle, configuration) + events := processHcsResult(ctx, resultJSON) + if err != nil { + return nil, nil, makeSystemError(computeSystem, operation, configuration, err, events) + } - logrus.WithFields(computeSystem.logctx). - WithField(logfields.JSON, configuration). - Debug("HCS ComputeSystem Process Document") + log.G(ctx).WithField("pid", processInfo.ProcessId).Debug("created process pid") + return newProcess(processHandle, int(processInfo.ProcessId), computeSystem), &processInfo, nil +} - syscallWatcher(computeSystem.logctx, func() { - err = hcsCreateProcess(computeSystem.handle, configuration, &processInfo, &processHandle, &resultp) - }) - events := processHcsResult(resultp) +// CreateProcessNoStdio launches a new process within the computeSystem. The +// Stdio handles are not cached on the process struct. +func (computeSystem *System) CreateProcessNoStdio(c interface{}) (_ cow.Process, err error) { + operation := "hcsshim::System::CreateProcessNoStdio" + ctx, span := trace.StartSpan(context.Background(), operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + + process, processInfo, err := computeSystem.createProcess(ctx, operation, c) if err != nil { - return nil, makeSystemError(computeSystem, "CreateProcess", configuration, err, events) + return nil, err } + defer func() { + if err != nil { + process.Close() + } + }() + + // We don't do anything with these handles. Close them so they don't leak. + syscall.Close(processInfo.StdInput) + syscall.Close(processInfo.StdOutput) + syscall.Close(processInfo.StdError) - logrus.WithFields(computeSystem.logctx). - WithField(logfields.ProcessID, processInfo.ProcessId). - Debug("HCS ComputeSystem CreateProcess PID") + if err = process.registerCallback(ctx); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) + } + go process.waitBackground() + + return process, nil +} + +// CreateProcess launches a new process within the computeSystem. +func (computeSystem *System) CreateProcess(ctx context.Context, c interface{}) (cow.Process, error) { + operation := "hcsshim::System::CreateProcess" + process, processInfo, err := computeSystem.createProcess(ctx, operation, c) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + process.Close() + } + }() - process := newProcess(processHandle, int(processInfo.ProcessId), computeSystem) - process.cachedPipes = &cachedPipes{ - stdIn: processInfo.StdInput, - stdOut: processInfo.StdOutput, - stdErr: processInfo.StdError, + pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError}) + if err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) } + process.stdin = pipes[0] + process.stdout = pipes[1] + process.stderr = pipes[2] - if err = process.registerCallback(); err != nil { - return nil, makeSystemError(computeSystem, "CreateProcess", "", err, nil) + if err = process.registerCallback(ctx); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) } + go process.waitBackground() return process, nil } // OpenProcess gets an interface to an existing process within the computeSystem. -func (computeSystem *System) OpenProcess(pid int) (_ *Process, err error) { +func (computeSystem *System) OpenProcess(ctx context.Context, pid int) (*Process, error) { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - // Add PID for the context of this operation - computeSystem.logctx[logfields.ProcessID] = pid - defer delete(computeSystem.logctx, logfields.ProcessID) - - operation := "hcsshim::ComputeSystem::OpenProcess" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - - var ( - processHandle hcsProcess - resultp *uint16 - ) + operation := "hcsshim::System::OpenProcess" if computeSystem.handle == 0 { - return nil, makeSystemError(computeSystem, "OpenProcess", "", ErrAlreadyClosed, nil) + return nil, makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } - syscallWatcher(computeSystem.logctx, func() { - err = hcsOpenProcess(computeSystem.handle, uint32(pid), &processHandle, &resultp) - }) - events := processHcsResult(resultp) + processHandle, resultJSON, err := vmcompute.HcsOpenProcess(ctx, computeSystem.handle, uint32(pid)) + events := processHcsResult(ctx, resultJSON) if err != nil { - return nil, makeSystemError(computeSystem, "OpenProcess", "", err, events) + return nil, makeSystemError(computeSystem, operation, "", err, events) } process := newProcess(processHandle, pid, computeSystem) - if err = process.registerCallback(); err != nil { - return nil, makeSystemError(computeSystem, "OpenProcess", "", err, nil) + if err = process.registerCallback(ctx); err != nil { + return nil, makeSystemError(computeSystem, operation, "", err, nil) } + go process.waitBackground() return process, nil } // Close cleans up any state associated with the compute system but does not terminate or wait for it. func (computeSystem *System) Close() (err error) { + operation := "hcsshim::System::Close" + ctx, span := trace.StartSpan(context.Background(), operation) + defer span.End() + defer func() { oc.SetSpanStatus(span, err) }() + span.AddAttributes(trace.StringAttribute("cid", computeSystem.id)) + computeSystem.handleLock.Lock() defer computeSystem.handleLock.Unlock() - operation := "hcsshim::ComputeSystem::Close" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() - // Don't double free this if computeSystem.handle == 0 { return nil } - if err = computeSystem.unregisterCallback(); err != nil { - return makeSystemError(computeSystem, "Close", "", err, nil) + if err = computeSystem.unregisterCallback(ctx); err != nil { + return makeSystemError(computeSystem, operation, "", err, nil) } - syscallWatcher(computeSystem.logctx, func() { - err = hcsCloseComputeSystem(computeSystem.handle) - }) + err = vmcompute.HcsCloseComputeSystem(ctx, computeSystem.handle) if err != nil { - return makeSystemError(computeSystem, "Close", "", err, nil) + return makeSystemError(computeSystem, operation, "", err, nil) } computeSystem.handle = 0 + computeSystem.closedWaitOnce.Do(func() { + computeSystem.waitError = ErrAlreadyClosed + close(computeSystem.waitBlock) + }) return nil } -func (computeSystem *System) registerCallback() error { - context := ¬ifcationWatcherContext{ - channels: newChannels(), +func (computeSystem *System) registerCallback(ctx context.Context) error { + callbackContext := ¬ifcationWatcherContext{ + channels: newSystemChannels(), + systemID: computeSystem.id, } callbackMapLock.Lock() callbackNumber := nextCallback nextCallback++ - callbackMap[callbackNumber] = context + callbackMap[callbackNumber] = callbackContext callbackMapLock.Unlock() - var callbackHandle hcsCallback - err := hcsRegisterComputeSystemCallback(computeSystem.handle, notificationWatcherCallback, callbackNumber, &callbackHandle) + callbackHandle, err := vmcompute.HcsRegisterComputeSystemCallback(ctx, computeSystem.handle, notificationWatcherCallback, callbackNumber) if err != nil { return err } - context.handle = callbackHandle + callbackContext.handle = callbackHandle computeSystem.callbackNumber = callbackNumber return nil } -func (computeSystem *System) unregisterCallback() error { +func (computeSystem *System) unregisterCallback(ctx context.Context) error { callbackNumber := computeSystem.callbackNumber callbackMapLock.RLock() - context := callbackMap[callbackNumber] + callbackContext := callbackMap[callbackNumber] callbackMapLock.RUnlock() - if context == nil { + if callbackContext == nil { return nil } - handle := context.handle + handle := callbackContext.handle if handle == 0 { return nil @@ -632,15 +644,15 @@ func (computeSystem *System) unregisterCallback() error { // hcsUnregisterComputeSystemCallback has its own syncronization // to wait for all callbacks to complete. We must NOT hold the callbackMapLock. - err := hcsUnregisterComputeSystemCallback(handle) + err := vmcompute.HcsUnregisterComputeSystemCallback(ctx, handle) if err != nil { return err } - closeChannels(context.channels) + closeChannels(callbackContext.channels) callbackMapLock.Lock() - callbackMap[callbackNumber] = nil + delete(callbackMap, callbackNumber) callbackMapLock.Unlock() handle = 0 @@ -649,36 +661,26 @@ func (computeSystem *System) unregisterCallback() error { } // Modify the System by sending a request to HCS -func (computeSystem *System) Modify(config interface{}) (err error) { +func (computeSystem *System) Modify(ctx context.Context, config interface{}) error { computeSystem.handleLock.RLock() defer computeSystem.handleLock.RUnlock() - operation := "hcsshim::ComputeSystem::Modify" - computeSystem.logOperationBegin(operation) - defer func() { computeSystem.logOperationEnd(operation, err) }() + operation := "hcsshim::System::Modify" if computeSystem.handle == 0 { - return makeSystemError(computeSystem, "Modify", "", ErrAlreadyClosed, nil) + return makeSystemError(computeSystem, operation, "", ErrAlreadyClosed, nil) } - requestJSON, err := json.Marshal(config) + requestBytes, err := json.Marshal(config) if err != nil { return err } - requestString := string(requestJSON) - - logrus.WithFields(computeSystem.logctx). - WithField(logfields.JSON, requestString). - Debug("HCS ComputeSystem Modify Document") - - var resultp *uint16 - syscallWatcher(computeSystem.logctx, func() { - err = hcsModifyComputeSystem(computeSystem.handle, requestString, &resultp) - }) - events := processHcsResult(resultp) + requestJSON := string(requestBytes) + resultJSON, err := vmcompute.HcsModifyComputeSystem(ctx, computeSystem.handle, requestJSON) + events := processHcsResult(ctx, resultJSON) if err != nil { - return makeSystemError(computeSystem, "Modify", requestString, err, events) + return makeSystemError(computeSystem, operation, requestJSON, err, events) } return nil |