github.com/ergo-services/ergo@v1.999.224/gen/tcp.go (about)

     1  package gen
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"strconv"
    10  	"sync/atomic"
    11  	"time"
    12  	"unsafe"
    13  
    14  	"github.com/ergo-services/ergo/etf"
    15  	"github.com/ergo-services/ergo/lib"
    16  )
    17  
    18  type TCPBehavior interface {
    19  	ServerBehavior
    20  
    21  	InitTCP(process *TCPProcess, args ...etf.Term) (TCPOptions, error)
    22  
    23  	HandleTCPCall(process *TCPProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus)
    24  	HandleTCPCast(process *TCPProcess, message etf.Term) ServerStatus
    25  	HandleTCPInfo(process *TCPProcess, message etf.Term) ServerStatus
    26  
    27  	HandleTCPTerminate(process *TCPProcess, reason string)
    28  }
    29  
    30  type TCPStatus error
    31  
    32  var (
    33  	TCPStatusOK   TCPStatus
    34  	TCPStatusStop TCPStatus = fmt.Errorf("stop")
    35  
    36  	defaultDeadlineTimeout int = 3
    37  	defaultDirectTimeout   int = 5
    38  )
    39  
    40  type TCP struct {
    41  	Server
    42  }
    43  
    44  type TCPOptions struct {
    45  	Host            string
    46  	Port            uint16
    47  	TLS             *tls.Config
    48  	KeepAlivePeriod int
    49  	Handler         TCPHandlerBehavior
    50  	// QueueLength defines how many parallel requests can be directed to this process. Default value is 10.
    51  	QueueLength int
    52  	// NumHandlers defines how many handlers will be started. Default 1
    53  	NumHandlers int
    54  	// IdleTimeout defines how long (in seconds) keeps the started handler alive with no packets. Zero value makes the handler non-stop.
    55  	IdleTimeout     int
    56  	DeadlineTimeout int
    57  	MaxPacketSize   int
    58  	// ExtraHandlers enables starting new handlers if all handlers in the pool are busy.
    59  	ExtraHandlers bool
    60  }
    61  
    62  type TCPProcess struct {
    63  	ServerProcess
    64  	options  TCPOptions
    65  	behavior TCPBehavior
    66  
    67  	pool     []*Process
    68  	counter  uint64
    69  	listener net.Listener
    70  }
    71  
    72  // Server callbacks
    73  func (tcp *TCP) Init(process *ServerProcess, args ...etf.Term) error {
    74  	behavior, ok := process.Behavior().(TCPBehavior)
    75  	if !ok {
    76  		return fmt.Errorf("not a TCPBehavior")
    77  	}
    78  
    79  	tcpProcess := &TCPProcess{
    80  		ServerProcess: *process,
    81  		behavior:      behavior,
    82  	}
    83  	// do not inherit parent State
    84  	tcpProcess.State = nil
    85  
    86  	options, err := behavior.InitTCP(tcpProcess, args...)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	if options.Handler == nil {
    91  		return fmt.Errorf("handler must be defined")
    92  	}
    93  	if options.DeadlineTimeout < 1 {
    94  		// we need to check the context if it was canceled to stop
    95  		// reading and close the connection socket
    96  		options.DeadlineTimeout = defaultDeadlineTimeout
    97  	}
    98  
    99  	tcpProcess.options = options
   100  	if err := tcpProcess.initHandlers(); err != nil {
   101  		return err
   102  	}
   103  
   104  	if options.Port == 0 {
   105  		return fmt.Errorf("TCP port must be defined")
   106  	}
   107  
   108  	lc := net.ListenConfig{}
   109  
   110  	if options.KeepAlivePeriod > 0 {
   111  		lc.KeepAlive = time.Duration(options.KeepAlivePeriod) * time.Second
   112  	}
   113  	ctx := process.Context()
   114  	hostPort := net.JoinHostPort("", strconv.Itoa(int(options.Port)))
   115  	listener, err := lc.Listen(ctx, "tcp", hostPort)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	if options.TLS != nil {
   121  		if options.TLS.Certificates == nil && options.TLS.GetCertificate == nil {
   122  			return fmt.Errorf("TLS connnnfig has no certificates")
   123  		}
   124  		listener = tls.NewListener(listener, options.TLS)
   125  	}
   126  	tcpProcess.listener = listener
   127  
   128  	// start acceptor
   129  	go func() {
   130  		var err error
   131  		var c net.Conn
   132  		defer func() {
   133  			if err == nil {
   134  				process.Exit("normal")
   135  				return
   136  			}
   137  			process.Exit(err.Error())
   138  		}()
   139  
   140  		for {
   141  			c, err = listener.Accept()
   142  			if err != nil {
   143  				if ctx.Err() == nil {
   144  					continue
   145  				}
   146  				return
   147  			}
   148  			go tcpProcess.serve(ctx, c)
   149  		}
   150  	}()
   151  
   152  	process.State = tcpProcess
   153  	return nil
   154  }
   155  
   156  func (tcp *TCP) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   157  	tcpp := process.State.(*TCPProcess)
   158  	return tcpp.behavior.HandleTCPCall(tcpp, from, message)
   159  }
   160  
   161  func (tcp *TCP) HandleCast(process *ServerProcess, message etf.Term) ServerStatus {
   162  	tcpp := process.State.(*TCPProcess)
   163  	return tcpp.behavior.HandleTCPCast(tcpp, message)
   164  }
   165  
   166  func (tcp *TCP) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus {
   167  	tcpp := process.State.(*TCPProcess)
   168  	return tcpp.behavior.HandleTCPInfo(tcpp, message)
   169  }
   170  
   171  func (tcp *TCP) Terminate(process *ServerProcess, reason string) {
   172  	p := process.State.(*TCPProcess)
   173  	p.listener.Close()
   174  	p.behavior.HandleTCPTerminate(p, reason)
   175  }
   176  
   177  //
   178  // default TCP callbacks
   179  //
   180  
   181  // HandleTCPCall
   182  func (tcp *TCP) HandleTCPCall(process *TCPProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   183  	lib.Warning("[gen.TCP] HandleTCPCall: unhandled message (from %#v) %#v", from, message)
   184  	return etf.Atom("ok"), ServerStatusOK
   185  }
   186  
   187  // HandleTCPCast
   188  func (tcp *TCP) HandleTCPCast(process *TCPProcess, message etf.Term) ServerStatus {
   189  	lib.Warning("[gen.TCP] HandleTCPCast: unhandled message %#v", message)
   190  	return ServerStatusOK
   191  }
   192  
   193  // HandleTCPInfo
   194  func (tcp *TCP) HandleTCPInfo(process *TCPProcess, message etf.Term) ServerStatus {
   195  	lib.Warning("[gen.TCP] HandleTCPInfo: unhandled message %#v", message)
   196  	return ServerStatusOK
   197  }
   198  func (tcp *TCP) HandleTCPTerminate(process *TCPProcess, reason string) {
   199  	return
   200  }
   201  
   202  // internal
   203  
   204  func (tcpp *TCPProcess) serve(ctx context.Context, c net.Conn) error {
   205  	var handlerProcess Process
   206  	var handlerProcessID int
   207  	var packet interface{}
   208  	var disconnect bool
   209  	var deadline bool
   210  	var timeout bool
   211  	var disconnectError error
   212  	var expectingBytes int = 1
   213  
   214  	defer c.Close()
   215  
   216  	deadlineTimeout := time.Second * time.Duration(tcpp.options.DeadlineTimeout)
   217  
   218  	tcpConnection := &TCPConnection{
   219  		Addr:   c.RemoteAddr(),
   220  		Socket: c,
   221  	}
   222  
   223  	l := uint64(tcpp.options.NumHandlers)
   224  	// make round robin using the counter value
   225  	cnt := atomic.AddUint64(&tcpp.counter, 1)
   226  	// choose process as a handler for the packet received on this connection
   227  	handlerProcessID = int(cnt % l)
   228  	handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tcpp.pool[handlerProcessID]))))
   229  
   230  	b := lib.TakeBuffer()
   231  
   232  nextPacket:
   233  	for {
   234  		if ctx.Err() != nil {
   235  			return nil
   236  		}
   237  
   238  		if packet == nil {
   239  			// just connected
   240  			packet = messageTCPHandlerConnect{
   241  				connection: tcpConnection,
   242  			}
   243  			break
   244  		}
   245  
   246  		if b.Len() < expectingBytes {
   247  			deadline = false
   248  			if err := c.SetReadDeadline(time.Now().Add(deadlineTimeout)); err == nil {
   249  				deadline = true
   250  			}
   251  
   252  			n, e := b.ReadDataFrom(c, tcpp.options.MaxPacketSize)
   253  			if n == 0 {
   254  				if err, ok := e.(net.Error); deadline && ok && err.Timeout() {
   255  					packet = messageTCPHandlerTimeout{
   256  						connection: tcpConnection,
   257  					}
   258  					timeout = true
   259  					break
   260  				}
   261  				packet = messageTCPHandlerDisconnect{
   262  					connection: tcpConnection,
   263  				}
   264  				// closed connection
   265  				disconnect = true
   266  				break
   267  			}
   268  
   269  			if e != nil && e != io.EOF {
   270  				// something went wrong
   271  				packet = messageTCPHandlerDisconnect{
   272  					connection: tcpConnection,
   273  				}
   274  				disconnect = true
   275  				disconnectError = e
   276  				break
   277  			}
   278  
   279  			// check onemore time if we should read more data
   280  			continue
   281  		}
   282  		// FIXME take it from the pool
   283  		packet = &messageTCPHandlerPacket{
   284  			connection: tcpConnection,
   285  			packet:     b.B,
   286  		}
   287  		break
   288  	}
   289  
   290  retry:
   291  	for a := uint64(0); a < l; a++ {
   292  		if ctx.Err() != nil {
   293  			return nil
   294  		}
   295  
   296  		nbytesInt, err := handlerProcess.DirectWithTimeout(packet, defaultDirectTimeout)
   297  		switch err {
   298  		case TCPHandlerStatusOK:
   299  			if disconnect {
   300  				return disconnectError
   301  			}
   302  			if timeout {
   303  				timeout = false
   304  				goto nextPacket
   305  			}
   306  			next, _ := nbytesInt.(messageTCPHandlerPacketResult)
   307  			if next.left > 0 {
   308  				if b.Len() > next.left {
   309  					b1 := lib.TakeBuffer()
   310  					head := b.Len() - next.left
   311  					b1.Set(b.B[head:])
   312  					lib.ReleaseBuffer(b)
   313  					b = b1
   314  				}
   315  			} else {
   316  				b.Reset()
   317  			}
   318  			expectingBytes = b.Len() + next.await
   319  			if expectingBytes == 0 {
   320  				expectingBytes++
   321  			}
   322  
   323  			goto nextPacket
   324  
   325  		case TCPHandlerStatusClose:
   326  			return disconnectError
   327  		case lib.ErrProcessTerminated:
   328  			if handlerProcessID == -1 {
   329  				// it was an extra handler do not restart. try to use the existing one
   330  				cnt = atomic.AddUint64(&tcpp.counter, 1)
   331  				handlerProcessID = int(cnt % l)
   332  				handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tcpp.pool[handlerProcessID]))))
   333  				goto retry
   334  			}
   335  
   336  			// respawn terminated process
   337  			handlerProcess = tcpp.startHandler(handlerProcessID, tcpp.options.IdleTimeout)
   338  			atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&tcpp.pool[handlerProcessID])), unsafe.Pointer(&handlerProcess))
   339  			continue
   340  		case lib.ErrProcessBusy:
   341  			handlerProcessID = int((a + cnt) % l)
   342  			handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tcpp.pool[handlerProcessID]))))
   343  			continue
   344  		default:
   345  			lib.Warning("[gen.TCP] error on handling packet: %s. closing connection with %q", err, c.RemoteAddr())
   346  			return err
   347  		}
   348  	}
   349  
   350  	// create a new handler. we should eather to make a call HandleDisconnect or
   351  	// run this connection with the extra handler with idle timeout = 5 second
   352  	handlerProcessID = -1
   353  	handlerProcess = tcpp.startHandler(handlerProcessID, 5)
   354  	if tcpp.options.ExtraHandlers == false {
   355  		packet = messageTCPHandlerDisconnect{
   356  			connection: tcpConnection,
   357  		}
   358  
   359  		handlerProcess.DirectWithTimeout(packet, defaultDirectTimeout)
   360  		lib.Warning("[gen.TCP] all handlers are busy. closing connection with %q", c.RemoteAddr())
   361  		handlerProcess.Kill()
   362  		return fmt.Errorf("all handlers are busy")
   363  	}
   364  
   365  	goto retry
   366  }
   367  
   368  func (tcpp *TCPProcess) initHandlers() error {
   369  	if tcpp.options.NumHandlers < 1 {
   370  		tcpp.options.NumHandlers = 1
   371  	}
   372  	if tcpp.options.IdleTimeout < 0 {
   373  		tcpp.options.IdleTimeout = 0
   374  	}
   375  
   376  	if tcpp.options.QueueLength < 1 {
   377  		tcpp.options.QueueLength = defaultQueueLength
   378  	}
   379  
   380  	c := atomic.AddUint64(&tcpp.counter, 1)
   381  	if c > 1 {
   382  		return fmt.Errorf("you can not use the same object more than once")
   383  	}
   384  
   385  	for i := 0; i < tcpp.options.NumHandlers; i++ {
   386  		p := tcpp.startHandler(i, tcpp.options.IdleTimeout)
   387  		if p == nil {
   388  			return fmt.Errorf("can not initialize handlers")
   389  		}
   390  		tcpp.pool = append(tcpp.pool, &p)
   391  	}
   392  	return nil
   393  }
   394  
   395  func (tcpp *TCPProcess) startHandler(id int, idleTimeout int) Process {
   396  	opts := ProcessOptions{
   397  		Context:       tcpp.Context(),
   398  		DirectboxSize: uint16(tcpp.options.QueueLength),
   399  	}
   400  
   401  	optsHandler := optsTCPHandler{id: id, idleTimeout: idleTimeout}
   402  	p, err := tcpp.Spawn("", opts, tcpp.options.Handler, optsHandler)
   403  	if err != nil {
   404  		lib.Warning("[gen.TCP] can not start TCPHandler: %s", err)
   405  		return nil
   406  	}
   407  	return p
   408  }