evio/evio_net.go

411 lines
7.9 KiB
Go

// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package evio
import (
"io"
"net"
"sort"
"sync"
"sync/atomic"
"time"
)
type netConn struct {
id int
wake int64
conn net.Conn
detached bool
outbuf []byte
err error
}
func (c *netConn) Read(p []byte) (n int, err error) {
return c.conn.Read(p)
}
func (c *netConn) Write(p []byte) (n int, err error) {
if c.detached {
if len(c.outbuf) > 0 {
for len(c.outbuf) > 0 {
n, err = c.conn.Write(c.outbuf)
if n > 0 {
c.outbuf = c.outbuf[n:]
}
if err != nil {
return 0, err
}
}
c.outbuf = nil
}
var tn int
if len(p) > 0 {
for len(p) > 0 {
n, err = c.conn.Write(p)
if n > 0 {
p = p[n:]
tn += n
}
if err != nil {
return tn, err
}
}
p = nil
}
return tn, nil
}
return c.conn.Write(p)
}
func (c *netConn) Close() error {
return c.conn.Close()
}
// servenet uses the stdlib net package instead of syscalls.
func servenet(events Events, lns []*listener) error {
var idc int64
var mu sync.Mutex
var cmu sync.Mutex
var idconn = make(map[int]*netConn)
var done int64
var shutdown func(err error)
// connloop handles an individual connection
connloop := func(id int, conn net.Conn, lnidx int, ln net.Listener) {
var closed bool
defer func() {
if !closed {
conn.Close()
}
}()
var packet [0xFFFF]byte
var cout []byte
var caction Action
c := &netConn{id: id, conn: conn}
cmu.Lock()
idconn[id] = c
cmu.Unlock()
if events.Opened != nil {
var out []byte
var opts Options
var action Action
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
out, opts, action = events.Opened(id, Info{
AddrIndex: lnidx,
LocalAddr: conn.LocalAddr(),
RemoteAddr: conn.RemoteAddr(),
})
}
mu.Unlock()
if opts.TCPKeepAlive > 0 {
if conn, ok := conn.(*net.TCPConn); ok {
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(opts.TCPKeepAlive)
}
}
if len(out) > 0 {
cout = append(cout, out...)
}
caction = action
}
for {
var n int
var err error
var out []byte
var action Action
if caction != None {
goto write
}
if len(cout) > 0 || atomic.LoadInt64(&c.wake) != 0 {
conn.SetReadDeadline(time.Now().Add(time.Microsecond))
} else {
conn.SetReadDeadline(time.Now().Add(time.Second))
}
n, err = c.Read(packet[:])
if err != nil && !istimeout(err) {
if err != io.EOF {
c.err = err
}
goto close
}
if n > 0 {
if events.Data != nil {
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
out, action = events.Data(id, append([]byte{}, packet[:n]...))
}
mu.Unlock()
}
} else if atomic.LoadInt64(&c.wake) != 0 {
atomic.StoreInt64(&c.wake, 0)
if events.Data != nil {
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
out, action = events.Data(id, nil)
}
mu.Unlock()
}
}
if len(out) > 0 {
cout = append(cout, out...)
}
caction = action
goto write
write:
if len(cout) > 0 {
if events.Prewrite != nil {
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
action = events.Prewrite(id, len(cout))
}
mu.Unlock()
if action == Shutdown {
caction = Shutdown
}
}
conn.SetWriteDeadline(time.Now().Add(time.Microsecond))
n, err := c.Write(cout)
if err != nil && !istimeout(err) {
if err != io.EOF {
c.err = err
}
goto close
}
cout = cout[n:]
if len(cout) == 0 {
cout = nil
}
if events.Postwrite != nil {
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
action = events.Postwrite(id, n, len(cout))
}
mu.Unlock()
if action == Shutdown {
caction = Shutdown
}
}
}
if caction == Shutdown {
goto close
}
if len(cout) == 0 {
if caction != None {
goto close
}
}
continue
close:
cmu.Lock()
delete(idconn, c.id)
cmu.Unlock()
mu.Lock()
if atomic.LoadInt64(&done) != 0 {
mu.Unlock()
return
}
mu.Unlock()
if caction == Detach {
if events.Detached != nil {
if len(cout) > 0 {
c.outbuf = cout
}
c.detached = true
conn.SetDeadline(time.Time{})
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
caction = events.Detached(c.id, c)
}
mu.Unlock()
closed = true
if caction == Shutdown {
goto fail
}
return
}
}
conn.Close()
if events.Closed != nil {
var action Action
mu.Lock()
if atomic.LoadInt64(&done) == 0 {
action = events.Closed(c.id, c.err)
}
mu.Unlock()
if action == Shutdown {
caction = Shutdown
}
}
closed = true
if caction == Shutdown {
goto fail
}
return
fail:
shutdown(nil)
return
}
}
ctx := Server{
Wake: func(id int) bool {
cmu.Lock()
c := idconn[id]
cmu.Unlock()
if c == nil {
return false
}
atomic.StoreInt64(&c.wake, 1)
// force a quick wakeup
c.conn.SetDeadline(time.Time{}.Add(1))
return true
},
Dial: func(addr string, timeout time.Duration) int {
if atomic.LoadInt64(&done) != 0 {
return 0
}
id := int(atomic.AddInt64(&idc, 1))
go func() {
network, address, _ := parseAddr(addr)
var conn net.Conn
var err error
if timeout > 0 {
conn, err = net.DialTimeout(network, address, timeout)
} else {
conn, err = net.Dial(network, address)
}
if err != nil {
if events.Opened != nil {
mu.Lock()
_, _, action := events.Opened(id, Info{Closing: true, AddrIndex: -1})
mu.Unlock()
if action == Shutdown {
shutdown(nil)
return
}
}
if events.Closed != nil {
mu.Lock()
action := events.Closed(id, err)
mu.Unlock()
if action == Shutdown {
shutdown(nil)
return
}
}
return
}
go connloop(id, conn, -1, nil)
}()
return id
},
}
var swg sync.WaitGroup
swg.Add(1)
var ferr error
shutdown = func(err error) {
mu.Lock()
if atomic.LoadInt64(&done) != 0 {
mu.Unlock()
return
}
defer swg.Done()
atomic.StoreInt64(&done, 1)
ferr = err
for _, ln := range lns {
ln.ln.Close()
}
type connid struct {
conn net.Conn
id int
}
var connids []connid
cmu.Lock()
for id, conn := range idconn {
connids = append(connids, connid{conn.conn, id})
}
idconn = make(map[int]*netConn)
cmu.Unlock()
mu.Unlock()
sort.Slice(connids, func(i, j int) bool {
return connids[j].id < connids[i].id
})
for _, connid := range connids {
connid.conn.Close()
if events.Closed != nil {
mu.Lock()
events.Closed(connid.id, nil)
mu.Unlock()
}
}
}
ctx.Addrs = make([]net.Addr, len(lns))
for i, ln := range lns {
ctx.Addrs[i] = ln.naddr
}
if events.Serving != nil {
if events.Serving(ctx) == Shutdown {
return nil
}
}
var lwg sync.WaitGroup
lwg.Add(len(lns))
for i, ln := range lns {
go func(lnidx int, ln net.Listener) {
defer lwg.Done()
for {
conn, err := ln.Accept()
if err != nil {
if err == io.EOF {
shutdown(nil)
} else {
shutdown(err)
}
return
}
id := int(atomic.AddInt64(&idc, 1))
go connloop(id, conn, lnidx, ln)
}
}(i, ln.ln)
}
go func() {
for {
mu.Lock()
if atomic.LoadInt64(&done) != 0 {
mu.Unlock()
break
}
mu.Unlock()
var delay time.Duration
var action Action
mu.Lock()
if events.Tick != nil {
if atomic.LoadInt64(&done) == 0 {
delay, action = events.Tick()
}
} else {
mu.Unlock()
break
}
mu.Unlock()
if action == Shutdown {
shutdown(nil)
return
}
time.Sleep(delay)
}
}()
lwg.Wait() // wait for listeners
swg.Wait() // wait for shutdown
return ferr
}
func istimeout(err error) bool {
if err, ok := err.(net.Error); ok && err.Timeout() {
return true
}
return false
}