Fix several small goroutine leaks around restart
This commit is contained in:
parent
a30e16354c
commit
8d0ed17e7f
|
@ -7,11 +7,14 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
path "path/filepath"
|
path "path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLaunchTor(t *testing.T) {
|
func TestLaunchTor(t *testing.T) {
|
||||||
|
goRoutineStart := runtime.NumGoroutine()
|
||||||
log.SetLevel(log.LevelDebug)
|
log.SetLevel(log.LevelDebug)
|
||||||
|
|
||||||
rand.Seed(int64(time.Now().Nanosecond()))
|
rand.Seed(int64(time.Now().Nanosecond()))
|
||||||
|
@ -51,4 +54,15 @@ func TestLaunchTor(t *testing.T) {
|
||||||
t.Log("we have bootstrapped!")
|
t.Log("we have bootstrapped!")
|
||||||
acn.Close()
|
acn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
|
goRoutineEnd := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
if goRoutineEnd != goRoutineStart {
|
||||||
|
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||||
|
|
||||||
|
t.Fatalf("goroutine leak in ACN: %v %v", goRoutineStart, goRoutineEnd)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package tor
|
package tor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -333,6 +334,7 @@ func (tp *torProvider) restart() {
|
||||||
tp.dialer = newTp.dialer
|
tp.dialer = newTp.dialer
|
||||||
tp.statusCallback = statusCallback
|
tp.statusCallback = statusCallback
|
||||||
tp.lastRestartTime = time.Now()
|
tp.lastRestartTime = time.Now()
|
||||||
|
tp.isClosed = false
|
||||||
go tp.monitorRestart()
|
go tp.monitorRestart()
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Error restarting Tor process: %v", err)
|
log.Errorf("Error restarting Tor process: %v", err)
|
||||||
|
@ -368,6 +370,8 @@ func (tp *torProvider) Close() {
|
||||||
delete(tp.childListeners, addr)
|
delete(tp.childListeners, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("shutting down acn threads..(is already closed: %v)", tp.isClosed)
|
||||||
|
|
||||||
if !tp.isClosed {
|
if !tp.isClosed {
|
||||||
// Break out of any background checks and close
|
// Break out of any background checks and close
|
||||||
// the underlying tor connection
|
// the underlying tor connection
|
||||||
|
@ -404,6 +408,7 @@ func (tp *torProvider) callStatusCallback(prog int, status string) {
|
||||||
func NewTorACNWithAuth(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) {
|
func NewTorACNWithAuth(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) {
|
||||||
tp, err := startTor(appDirectory, bundledTorPath, dataDir, controlPort, authenticator)
|
tp, err := startTor(appDirectory, bundledTorPath, dataDir, controlPort, authenticator)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
tp.isClosed = false
|
||||||
go tp.monitorRestart()
|
go tp.monitorRestart()
|
||||||
}
|
}
|
||||||
return tp, err
|
return tp, err
|
||||||
|
@ -450,7 +455,7 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro
|
||||||
|
|
||||||
os.MkdirAll(torDir, 0700)
|
os.MkdirAll(torDir, 0700)
|
||||||
|
|
||||||
tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)}
|
tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool, 1), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)}
|
||||||
|
|
||||||
log.Debugf("checking if there is a running system tor")
|
log.Debugf("checking if there is a running system tor")
|
||||||
if err := tp.checkVersion(); err == nil {
|
if err := tp.checkVersion(); err == nil {
|
||||||
|
@ -494,7 +499,7 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro
|
||||||
|
|
||||||
err := tp.checkVersion()
|
err := tp.checkVersion()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tp.t.DeleteDataDirOnClose = false // caller is repsonsible for dealing with cached information...
|
tp.t.DeleteDataDirOnClose = false // caller is responsible for dealing with cached information...
|
||||||
tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator})
|
tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator})
|
||||||
return tp, err
|
return tp, err
|
||||||
}
|
}
|
||||||
|
@ -567,24 +572,53 @@ func createFromExisting(controlport *control.Conn, datadir string) *tor.Tor {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkCmdlineTorVersion(torCmd string) bool {
|
func checkCmdlineTorVersion(torCmd string) bool {
|
||||||
|
// ideally we would use CommandContext with Timeout here
|
||||||
|
// but it doesn't work with programs that may launch other processes via scripts e.g. exec
|
||||||
|
// and the workout is more complex than just implementing the logic ourselves...
|
||||||
cmd := exec.Command(torCmd, "--version")
|
cmd := exec.Command(torCmd, "--version")
|
||||||
cmd.SysProcAttr = sysProcAttr
|
var outb bytes.Buffer
|
||||||
out, err := cmd.CombinedOutput()
|
cmd.Stdout = &outb
|
||||||
|
|
||||||
|
waiting := make(chan error, 1)
|
||||||
|
|
||||||
|
// try running the tor process
|
||||||
|
go func() {
|
||||||
|
log.Debugf("running tor process: %v", torCmd)
|
||||||
|
cmd.Run()
|
||||||
|
waiting <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// timeout function
|
||||||
|
go func() {
|
||||||
|
<-time.After(time.Second * 5)
|
||||||
|
waiting <- errors.New("timeout")
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := <-waiting
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("tor process timed out")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile(`[0-1]\.[0-9]\.[0-9]\.[0-9]`)
|
re := regexp.MustCompile(`[0-1]\.[0-9]\.[0-9]\.[0-9]`)
|
||||||
sysTorVersion := re.Find(out)
|
sysTorVersion := re.Find(outb.Bytes())
|
||||||
log.Infoln("tor version: " + string(sysTorVersion))
|
log.Infof("tor version: %v", string(sysTorVersion))
|
||||||
return err == nil && minTorVersionReqs(string(sysTorVersion))
|
return minTorVersionReqs(string(sysTorVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if supplied version meets our min requirments
|
// returns true if supplied version meets our min requirments
|
||||||
// min requirement: 0.3.5.x
|
// min requirement: 0.3.5.x
|
||||||
func minTorVersionReqs(torversion string) bool {
|
func minTorVersionReqs(torversion string) bool {
|
||||||
torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha
|
torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha
|
||||||
log.Debugf("torversions: %v", torversions)
|
if len(torversions) >= 3 {
|
||||||
tva, _ := strconv.Atoi(torversions[0])
|
log.Debugf("torversions: %v", torversions)
|
||||||
tvb, _ := strconv.Atoi(torversions[1])
|
tva, _ := strconv.Atoi(torversions[0])
|
||||||
tvc, _ := strconv.Atoi(torversions[2])
|
tvb, _ := strconv.Atoi(torversions[1])
|
||||||
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5)))
|
tvc, _ := strconv.Atoi(torversions[2])
|
||||||
|
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5)))
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialControlPort(port int) (*control.Conn, error) {
|
func dialControlPort(port int) (*control.Conn, error) {
|
||||||
|
|
|
@ -4,8 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
path "path/filepath"
|
path "path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStatusCallback(progChan chan int) func(int, string) {
|
func getStatusCallback(progChan chan int) func(int, string) {
|
||||||
|
@ -16,7 +20,10 @@ func getStatusCallback(progChan chan int) func(int, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTorProvider(t *testing.T) {
|
func TestTorProvider(t *testing.T) {
|
||||||
progChan := make(chan int)
|
|
||||||
|
goRoutineStart := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
progChan := make(chan int, 10)
|
||||||
log.SetLevel(log.LevelDebug)
|
log.SetLevel(log.LevelDebug)
|
||||||
torpath := path.Join("..", "tmp/tor")
|
torpath := path.Join("..", "tmp/tor")
|
||||||
|
|
||||||
|
@ -79,4 +86,15 @@ func TestTorProvider(t *testing.T) {
|
||||||
acn.Restart()
|
acn.Restart()
|
||||||
|
|
||||||
acn.Close()
|
acn.Close()
|
||||||
|
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
|
goRoutineEnd := runtime.NumGoroutine()
|
||||||
|
|
||||||
|
if goRoutineEnd != goRoutineStart {
|
||||||
|
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||||
|
|
||||||
|
t.Fatalf("goroutine leak in ACN: %v %v", goRoutineStart, goRoutineEnd)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue