Skip to content

Commit

Permalink
Merge branch 'release/0.11.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
takama committed Apr 3, 2018
2 parents fbd475b + cc5cf79 commit aa76b00
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 20 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ See `examples/cron/cron_job.go`
- [Maximus](https://github.com/maximus12793)
- [AlgorathDev](https://github.com/AlgorathDev)
- [Alexis Camilleri](https://github.com/krysennn)
- [neverland4u](https://github.com/neverland4u)

All the contributors are welcome. If you would like to be the contributor please accept some rules.

Expand Down
15 changes: 14 additions & 1 deletion daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.

/*
Package daemon 0.10.4 for use with Go (golang) services.
Package daemon 0.11.0 for use with Go (golang) services.
Package daemon provides primitives for daemonization of golang services.
This package is not provide implementation of user daemon,
Expand Down Expand Up @@ -174,6 +174,19 @@ type Daemon interface {

// Status - check the service status
Status() (string, error)

// Run - run executable service
Run(e Executable) (string, error)
}

// Executable interface defines controlling methods of executable service
type Executable interface {
// Start - non-blocking start service
Start()
// Stop - non-blocking stop service
Stop()
// Run - blocking run service
Run()
}

// New - Create a new daemon
Expand Down
7 changes: 7 additions & 0 deletions daemon_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ func (darwin *darwinRecord) Status() (string, error) {
return statusAction, nil
}

// Run - Run service
func (darwin *darwinRecord) Run(e Executable) (string, error) {
runAction := "Running " + darwin.description + ":"
e.Run()
return runAction + " completed.", nil
}

var propertyList = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Expand Down
7 changes: 7 additions & 0 deletions daemon_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ func (bsd *bsdRecord) Status() (string, error) {
return statusAction, nil
}

// Run - Run service
func (bsd *bsdRecord) Run(e Executable) (string, error) {
runAction := "Running " + bsd.description + ":"
e.Run()
return runAction + " completed.", nil
}

var bsdConfig = `#!/bin/sh
#
# PROVIDE: {{.Name}}
Expand Down
7 changes: 7 additions & 0 deletions daemon_linux_systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ func (linux *systemDRecord) Status() (string, error) {
return statusAction, nil
}

// Run - Run service
func (linux *systemDRecord) Run(e Executable) (string, error) {
runAction := "Running " + linux.description + ":"
e.Run()
return runAction + " completed.", nil
}

var systemDConfig = `[Unit]
Description={{.Description}}
Requires={{.Dependencies}}
Expand Down
7 changes: 7 additions & 0 deletions daemon_linux_systemv.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ func (linux *systemVRecord) Status() (string, error) {
return statusAction, nil
}

// Run - Run service
func (linux *systemVRecord) Run(e Executable) (string, error) {
runAction := "Running " + linux.description + ":"
e.Run()
return runAction + " completed.", nil
}

var systemVConfig = `#! /bin/sh
#
# /etc/rc.d/init.d/{{.Name}}
Expand Down
7 changes: 7 additions & 0 deletions daemon_linux_upstart.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ func (linux *upstartRecord) Status() (string, error) {
return statusAction, nil
}

// Run - Run service
func (linux *upstartRecord) Run(e Executable) (string, error) {
runAction := "Running " + linux.description + ":"
e.Run()
return runAction + " completed.", nil
}

var upstatConfig = `# {{.Name}} {{.Description}}
description "{{.Description}}"
Expand Down
227 changes: 208 additions & 19 deletions daemon_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ import (
"errors"
"fmt"
"os/exec"
"regexp"
"strconv"
"syscall"
"time"
"unicode/utf16"
"unsafe"

"golang.org/x/sys/windows/registry"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)

// windowsRecord - standard record (struct) for windows version of daemon package
Expand All @@ -37,58 +42,160 @@ func (windows *windowsRecord) Install(args ...string) (string, error) {
return installAction + failed, err
}

cmdArgs := []string{"create", windows.name, "start=auto", "binPath=" + execp}
cmdArgs = append(cmdArgs, args...)
m, err := mgr.Connect()
if err != nil {
return installAction + failed, err
}
defer m.Disconnect()

s, err := m.OpenService(windows.name)
if err == nil {
s.Close()
return installAction + failed, err
}

cmd := exec.Command("sc", cmdArgs...)
_, err = cmd.Output()
s, err = m.CreateService(windows.name, execp, mgr.Config{
DisplayName: windows.name,
Description: windows.description,
StartType: mgr.StartAutomatic,
Dependencies: windows.dependencies,
}, args...)
if err != nil {
return installAction + failed, getWindowsError(err)
return installAction + failed, err
}
defer s.Close()

return installAction + " completed.", nil
}

// Remove the service
func (windows *windowsRecord) Remove() (string, error) {
removeAction := "Removing " + windows.description + ":"
cmd := exec.Command("sc", "delete", windows.name, "confirm")
err := cmd.Run()

m, err := mgr.Connect()
if err != nil {
return removeAction + failed, getWindowsError(err)
}
defer m.Disconnect()
s, err := m.OpenService(windows.name)
if err != nil {
return removeAction + failed, getWindowsError(err)
}
defer s.Close()
err = s.Delete()
if err != nil {
return removeAction + failed, getWindowsError(err)
}

return removeAction + " completed.", nil
}

// Start the service
func (windows *windowsRecord) Start() (string, error) {
startAction := "Starting " + windows.description + ":"
cmd := exec.Command("sc", "start", windows.name)
err := cmd.Run()

m, err := mgr.Connect()
if err != nil {
return startAction + failed, getWindowsError(err)
}
defer m.Disconnect()
s, err := m.OpenService(windows.name)
if err != nil {
return startAction + failed, getWindowsError(err)
}
defer s.Close()
if err = s.Start(); err != nil {
return startAction + failed, getWindowsError(err)
}

return startAction + " completed.", nil
}

// Stop the service
func (windows *windowsRecord) Stop() (string, error) {
stopAction := "Stopping " + windows.description + ":"
cmd := exec.Command("sc", "stop", windows.name)
err := cmd.Run()

m, err := mgr.Connect()
if err != nil {
return stopAction + failed, getWindowsError(err)
}
defer m.Disconnect()
s, err := m.OpenService(windows.name)
if err != nil {
return stopAction + failed, getWindowsError(err)
}
defer s.Close()
if err := stopAndWait(s); err != nil {
return stopAction + failed, getWindowsError(err)
}

return stopAction + " completed.", nil
}

func stopAndWait(s *mgr.Service) error {
// First stop the service. Then wait for the service to
// actually stop before starting it.
status, err := s.Control(svc.Stop)
if err != nil {
return err
}

timeDuration := time.Millisecond * 50

timeout := time.After(getStopTimeout() + (timeDuration * 2))
tick := time.NewTicker(timeDuration)
defer tick.Stop()

for status.State != svc.Stopped {
select {
case <-tick.C:
status, err = s.Query()
if err != nil {
return err
}
case <-timeout:
break
}
}
return nil
}

func getStopTimeout() time.Duration {
// For default and paths see https://support.microsoft.com/en-us/kb/146092
defaultTimeout := time.Millisecond * 20000
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
if err != nil {
return defaultTimeout
}
sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
if err != nil {
return defaultTimeout
}
v, err := strconv.Atoi(sv)
if err != nil {
return defaultTimeout
}
return time.Millisecond * time.Duration(v)
}

// Status - Get service status
func (windows *windowsRecord) Status() (string, error) {
cmd := exec.Command("sc", "query", windows.name)
out, err := cmd.Output()
m, err := mgr.Connect()
if err != nil {
return "Getting status:" + failed, getWindowsError(err)
}
defer m.Disconnect()
s, err := m.OpenService(windows.name)
if err != nil {
return "Getting status:" + failed, getWindowsError(err)
}
return "Status: " + "SERVICE_" + getWindowsServiceState(out), nil
defer s.Close()
status, err := s.Query()
if err != nil {
return "Getting status:" + failed, getWindowsError(err)
}

return "Status: " + getWindowsServiceStateFromUint32(status.State), nil
}

// Get executable path
Expand Down Expand Up @@ -123,9 +230,91 @@ func getWindowsError(inputError error) error {
}

// Get windows service state
func getWindowsServiceState(out []byte) string {
regex := regexp.MustCompile("STATE.*: (?P<state_code>[0-9]) (?P<state>.*) ")
service := regex.FindAllStringSubmatch(string(out), -1)[0]
func getWindowsServiceStateFromUint32(state svc.State) string {
switch state {
case svc.Stopped:
return "SERVICE_STOPPED"
case svc.StartPending:
return "SERVICE_START_PENDING"
case svc.StopPending:
return "SERVICE_STOP_PENDING"
case svc.Running:
return "SERVICE_RUNNING"
case svc.ContinuePending:
return "SERVICE_CONTINUE_PENDING"
case svc.PausePending:
return "SERVICE_PAUSE_PENDING"
case svc.Paused:
return "SERVICE_PAUSED"
}
return "SERVICE_UNKNOWN"
}

type serviceHandler struct {
executable Executable
}

func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
changes <- svc.Status{State: svc.StartPending}

fasttick := time.Tick(500 * time.Millisecond)
slowtick := time.Tick(2 * time.Second)
tick := fasttick

sh.executable.Start()
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}

loop:
for {
select {
case <-tick:
break
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
sh.executable.Stop()
break loop
case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
tick = slowtick
case svc.Continue:
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
tick = fasttick
default:
continue loop
}
}
}
return
}

func (windows *windowsRecord) Run(e Executable) (string, error) {
runAction := "Running " + windows.description + ":"

interactive, err := svc.IsAnInteractiveSession()
if err != nil {
return runAction + failed, getWindowsError(err)
}
if !interactive {
// service called from windows service manager
// use API provided by golang.org/x/sys/windows
err = svc.Run(windows.name, &serviceHandler{
executable: e,
})
if err != nil {
return runAction + failed, getWindowsError(err)
}
} else {
// otherwise, service should be called from terminal session
e.Run()
}

return service[2]
return runAction + " completed.", nil
}

0 comments on commit aa76b00

Please sign in to comment.