Make Shaker's methods goroutine safe

This commit is contained in:
Tevin Zhang 2016-06-01 17:45:24 +08:00
parent 0c7007486a
commit f6beed899b
2 changed files with 34 additions and 2 deletions

7
err.go
View File

@ -1,8 +1,15 @@
package tcp package tcp
import (
"errors"
)
// ErrTimeout indicates I/O timeout // ErrTimeout indicates I/O timeout
var ErrTimeout = &timeoutError{} var ErrTimeout = &timeoutError{}
// ErrNotInitialized occurs while the Shaker is not initialized
var ErrNotInitialized = errors.New("not initialized")
type timeoutError struct{} type timeoutError struct{}
func (e *timeoutError) Error() string { return "I/O timeout" } func (e *timeoutError) Error() string { return "I/O timeout" }

View File

@ -26,12 +26,15 @@
// Usually this means the server will not notice the health checking // Usually this means the server will not notice the health checking
// traffic at all, thus the act of health chekcing will not be // traffic at all, thus the act of health chekcing will not be
// considered as some misbehaviour of client. // considered as some misbehaviour of client.
//
// Shaker's methods may be called by multiple goroutines simultaneously.
package tcp package tcp
import ( import (
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"time" "time"
) )
@ -40,12 +43,19 @@ const maxEpollEvents = 32
// Shaker contains an epoll instance for TCP handshake checking // Shaker contains an epoll instance for TCP handshake checking
type Shaker struct { type Shaker struct {
sync.RWMutex
epollFd int epollFd int
} }
// Init creates inner epoll instance, call this before anything else // Init creates inner epoll instance, call this before anything else
func (s *Shaker) Init() error { func (s *Shaker) Init() error {
var err error var err error
s.Lock()
defer s.Unlock()
if s.epollFd > 0 {
return nil
}
s.epollFd, err = syscall.EpollCreate1(0) s.epollFd, err = syscall.EpollCreate1(0)
if err != nil { if err != nil {
return os.NewSyscallError("epoll_create1", err) return os.NewSyscallError("epoll_create1", err)
@ -101,13 +111,22 @@ func (s *Shaker) Test(addr string, timeout time.Duration) error {
// Close closes the inner epoll fd // Close closes the inner epoll fd
func (s *Shaker) Close() error { func (s *Shaker) Close() error {
return syscall.Close(s.epollFd) s.Lock()
defer s.Unlock()
if s.epollFd > 0 {
err := syscall.Close(s.epollFd)
s.epollFd = 0
return err
}
return nil
} }
func (s *Shaker) addEpoll(fd int) error { func (s *Shaker) addEpoll(fd int) error {
var event syscall.EpollEvent var event syscall.EpollEvent
event.Events = syscall.EPOLLOUT event.Events = syscall.EPOLLOUT
event.Fd = int32(fd) event.Fd = int32(fd)
s.RLock()
defer s.RUnlock()
if err := syscall.EpollCtl(s.epollFd, syscall.EPOLL_CTL_ADD, fd, &event); err != nil { if err := syscall.EpollCtl(s.epollFd, syscall.EPOLL_CTL_ADD, fd, &event); err != nil {
return os.NewSyscallError("epoll_ctl", err) return os.NewSyscallError("epoll_ctl", err)
} }
@ -118,7 +137,13 @@ func (s *Shaker) addEpoll(fd int) error {
// The boolean returned indicates whether the connect is successful // The boolean returned indicates whether the connect is successful
func (s *Shaker) wait(fd int, timeoutMS int) (bool, error) { func (s *Shaker) wait(fd int, timeoutMS int) (bool, error) {
var events [maxEpollEvents]syscall.EpollEvent var events [maxEpollEvents]syscall.EpollEvent
nevents, err := syscall.EpollWait(s.epollFd, events[:], timeoutMS) s.RLock()
epollFd := s.epollFd
if epollFd <= 0 {
return false, ErrNotInitialized
}
s.RUnlock()
nevents, err := syscall.EpollWait(epollFd, events[:], timeoutMS)
if err != nil { if err != nil {
return false, os.NewSyscallError("epoll_wait", err) return false, os.NewSyscallError("epoll_wait", err)
} }