Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows 系统支持 #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ require (
github.com/oklog/run v1.1.0
github.com/pkg/errors v0.9.1
github.com/spiral/goridge/v2 v2.4.4
go.mongodb.org/mongo-driver v1.3.3
go.mongodb.org/mongo-driver v1.12.1
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down
4 changes: 4 additions & 0 deletions pkg/gotask/process_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//go:build !windows
// +build !windows

package gotask
181 changes: 181 additions & 0 deletions pkg/gotask/process_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

//go:build windows
// +build windows

package gotask

import (
"errors"
"os"
"syscall"
"unsafe"

"golang.org/x/sys/windows"
)

const (
ERROR_INVALID_PARAMETER = syscall.Errno(87)

STATUS_CANCELLED = uint32(0xC0000120)

processTerminateWaitInMs = 1000

killChildsPassCount = 4
)

var (
errFinishedProcess = errors.New("os: process already finished")
)

// FindProcess looks for a running process by its pid
func FindProcess(pid int) (*os.Process, error) {
var h syscall.Handle

process, err := os.FindProcess(pid)
if err != nil {
if isInvalidParameterError(err) { // NOTE: See function definition for details
return nil, nil
}
return nil, err
}

// If we have a process, check if it is terminated
h, err = syscall.OpenProcess(syscall.SYNCHRONIZE, false, uint32(pid))
if err == nil {
defer func() {
_ = syscall.CloseHandle(h)
}()

ret, e2 := syscall.WaitForSingleObject(h, 0)
if e2 == nil && ret == syscall.WAIT_OBJECT_0 {
return nil, nil
}
} else {
if isInvalidParameterError(err) { // NOTE: See function definition for details
return nil, nil
}
}

return process, nil
}

// KillProcess kills a running OS process
func KillProcess(pid int, signal os.Signal) error {
// Signal(0) only checks if we have access to kill a process and if it is really dead
if signal == syscall.Signal(0) {
return isProcessAlive(pid)
}

return killProcessTree(pid)
}

func isProcessAlive(pid int) error {
var ret uint32

h, err := syscall.OpenProcess(syscall.SYNCHRONIZE|syscall.PROCESS_TERMINATE, false, uint32(pid))
if err != nil {
if isInvalidParameterError(err) { // NOTE: See function definition for details
return errFinishedProcess
}
return err
}
ret, err = syscall.WaitForSingleObject(h, 0)
if err == nil && ret == syscall.WAIT_OBJECT_0 {
err = errFinishedProcess
}

_ = syscall.CloseHandle(h)
return err
}

func killProcessTree(pid int) error {
err := killProcess(pid)
if err != nil {
return err
}

// We do several passes just in case the process being killed spawns a new one
for pass := 1; pass <= killChildsPassCount; pass++ {
childProcessList := getChildProcesses(pid)
if len(childProcessList) == 0 {
break
}
for _, childPid := range childProcessList {
killProcessTree(childPid)
}
}

return nil
}

func getChildProcesses(pid int) []int {
var pe32 windows.ProcessEntry32

out := make([]int, 0)

snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, uint32(0))
if err != nil {
return out
}

defer func() {
_ = windows.CloseHandle(snap)
}()

pe32.Size = uint32(unsafe.Sizeof(pe32))
err = windows.Process32First(snap, &pe32)
for err != nil {
if pe32.ParentProcessID == uint32(pid) {
// Add to list
out = append(out, int(pe32.ProcessID))
}

err = windows.Process32Next(snap, &pe32)
}

return out
}

func killProcess(pid int) error {
h, err := syscall.OpenProcess(syscall.SYNCHRONIZE|syscall.PROCESS_TERMINATE, false, uint32(pid))
if err == nil {
err = syscall.TerminateProcess(h, STATUS_CANCELLED)
if err == nil {
_, _ = syscall.WaitForSingleObject(h, processTerminateWaitInMs)
}

_ = syscall.CloseHandle(h)
}

return err
}

// NOTE: Unlike Unix, Windows tries to open the target process in order to kill it.
//
// ERROR_INVALID_PARAMETER is returned if the process does not exists.
// To mimic other OS behavior, if the process does not exist, don't return an error
func isInvalidParameterError(err error) bool {
var syscallError syscall.Errno

if errors.As(err, &syscallError) {
if syscallError == ERROR_INVALID_PARAMETER {
return true
}
}
return false
}
3 changes: 3 additions & 0 deletions pkg/gotask/server.go → pkg/gotask/server_default.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build !windows
// +build !windows

package gotask

import (
Expand Down
3 changes: 3 additions & 0 deletions pkg/gotask/server_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build !windows
// +build !windows

package gotask

import (
Expand Down
Loading