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

     1  package gen
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net"
     7  	"strconv"
     8  	"sync/atomic"
     9  	"time"
    10  	"unsafe"
    11  
    12  	"github.com/ergo-services/ergo/etf"
    13  	"github.com/ergo-services/ergo/lib"
    14  )
    15  
    16  type UDPBehavior interface {
    17  	ServerBehavior
    18  
    19  	InitUDP(process *UDPProcess, args ...etf.Term) (UDPOptions, error)
    20  
    21  	HandleUDPCall(process *UDPProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus)
    22  	HandleUDPCast(process *UDPProcess, message etf.Term) ServerStatus
    23  	HandleUDPInfo(process *UDPProcess, message etf.Term) ServerStatus
    24  
    25  	HandleUDPTerminate(process *UDPProcess, reason string)
    26  }
    27  
    28  type UDPStatus error
    29  
    30  var (
    31  	UDPStatusOK   UDPStatus
    32  	UDPStatusStop UDPStatus = fmt.Errorf("stop")
    33  
    34  	defaultUDPDeadlineTimeout int = 3
    35  	defaultUDPQueueLength     int = 10
    36  	defaultUDPMaxPacketSize       = int(65000)
    37  )
    38  
    39  type UDP struct {
    40  	Server
    41  }
    42  
    43  type UDPOptions struct {
    44  	Host string
    45  	Port uint16
    46  
    47  	Handler         UDPHandlerBehavior
    48  	NumHandlers     int
    49  	IdleTimeout     int
    50  	DeadlineTimeout int
    51  	QueueLength     int
    52  	MaxPacketSize   int
    53  	ExtraHandlers   bool
    54  }
    55  
    56  type UDPProcess struct {
    57  	ServerProcess
    58  	options  UDPOptions
    59  	behavior UDPBehavior
    60  
    61  	pool       []*Process
    62  	counter    uint64
    63  	packetConn net.PacketConn
    64  }
    65  
    66  type UDPPacket struct {
    67  	Addr   net.Addr
    68  	Socket io.Writer
    69  }
    70  
    71  // Server callbacks
    72  func (udp *UDP) Init(process *ServerProcess, args ...etf.Term) error {
    73  
    74  	behavior := process.Behavior().(UDPBehavior)
    75  	behavior, ok := process.Behavior().(UDPBehavior)
    76  	if !ok {
    77  		return fmt.Errorf("not a UDPBehavior")
    78  	}
    79  
    80  	udpProcess := &UDPProcess{
    81  		ServerProcess: *process,
    82  		behavior:      behavior,
    83  	}
    84  	// do not inherit parent State
    85  	udpProcess.State = nil
    86  
    87  	options, err := behavior.InitUDP(udpProcess, args...)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	if options.Handler == nil {
    92  		return fmt.Errorf("handler must be defined")
    93  	}
    94  
    95  	if options.QueueLength == 0 {
    96  		options.QueueLength = defaultUDPQueueLength
    97  	}
    98  
    99  	if options.DeadlineTimeout < 1 {
   100  		// we need to check the context if it was canceled to stop
   101  		// reading and close the connection socket
   102  		options.DeadlineTimeout = defaultUDPDeadlineTimeout
   103  	}
   104  
   105  	if options.MaxPacketSize == 0 {
   106  		options.MaxPacketSize = defaultUDPMaxPacketSize
   107  	}
   108  
   109  	udpProcess.options = options
   110  	if err := udpProcess.initHandlers(); err != nil {
   111  		return err
   112  	}
   113  
   114  	if options.Port == 0 {
   115  		return fmt.Errorf("UDP port must be defined")
   116  	}
   117  
   118  	lc := net.ListenConfig{}
   119  	hostPort := net.JoinHostPort("", strconv.Itoa(int(options.Port)))
   120  	pconn, err := lc.ListenPacket(process.Context(), "udp", hostPort)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	udpProcess.packetConn = pconn
   126  	process.State = udpProcess
   127  
   128  	// start serving
   129  	go udpProcess.serve()
   130  	return nil
   131  }
   132  
   133  func (udp *UDP) HandleCall(process *ServerProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   134  	udpp := process.State.(*UDPProcess)
   135  	return udpp.behavior.HandleUDPCall(udpp, from, message)
   136  }
   137  
   138  func (udp *UDP) HandleCast(process *ServerProcess, message etf.Term) ServerStatus {
   139  	udpp := process.State.(*UDPProcess)
   140  	return udpp.behavior.HandleUDPCast(udpp, message)
   141  }
   142  
   143  func (udp *UDP) HandleInfo(process *ServerProcess, message etf.Term) ServerStatus {
   144  	udpp := process.State.(*UDPProcess)
   145  	return udpp.behavior.HandleUDPInfo(udpp, message)
   146  }
   147  
   148  func (udp *UDP) Terminate(process *ServerProcess, reason string) {
   149  	p := process.State.(*UDPProcess)
   150  	p.packetConn.Close()
   151  	p.behavior.HandleUDPTerminate(p, reason)
   152  }
   153  
   154  //
   155  // default UDP callbacks
   156  //
   157  
   158  // HandleUDPCall
   159  func (udp *UDP) HandleUDPCall(process *UDPProcess, from ServerFrom, message etf.Term) (etf.Term, ServerStatus) {
   160  	lib.Warning("[gen.UDP] HandleUDPCall: unhandled message (from %#v) %#v", from, message)
   161  	return etf.Atom("ok"), ServerStatusOK
   162  }
   163  
   164  // HandleUDPCast
   165  func (udp *UDP) HandleUDPCast(process *UDPProcess, message etf.Term) ServerStatus {
   166  	lib.Warning("[gen.UDP] HandleUDPCast: unhandled message %#v", message)
   167  	return ServerStatusOK
   168  }
   169  
   170  // HandleUDPInfo
   171  func (udp *UDP) HandleUDPInfo(process *UDPProcess, message etf.Term) ServerStatus {
   172  	lib.Warning("[gen.UDP] HandleUDPInfo: unhandled message %#v", message)
   173  	return ServerStatusOK
   174  }
   175  func (udp *UDP) HandleUDPTerminate(process *UDPProcess, reason string) {
   176  	return
   177  }
   178  
   179  // internals
   180  
   181  func (udpp *UDPProcess) initHandlers() error {
   182  	if udpp.options.NumHandlers < 1 {
   183  		udpp.options.NumHandlers = 1
   184  	}
   185  	if udpp.options.IdleTimeout < 0 {
   186  		udpp.options.IdleTimeout = 0
   187  	}
   188  
   189  	c := atomic.AddUint64(&udpp.counter, 1)
   190  	if c > 1 {
   191  		return fmt.Errorf("you can not use the same object more than once")
   192  	}
   193  
   194  	for i := 0; i < udpp.options.NumHandlers; i++ {
   195  		p := udpp.startHandler(i, udpp.options.IdleTimeout)
   196  		if p == nil {
   197  			return fmt.Errorf("can not initialize handlers")
   198  		}
   199  		udpp.pool = append(udpp.pool, &p)
   200  	}
   201  	return nil
   202  }
   203  
   204  func (udpp *UDPProcess) startHandler(id int, idleTimeout int) Process {
   205  	opts := ProcessOptions{
   206  		Context:     udpp.Context(),
   207  		MailboxSize: uint16(udpp.options.QueueLength),
   208  	}
   209  
   210  	optsHandler := optsUDPHandler{id: id, idleTimeout: idleTimeout}
   211  	p, err := udpp.Spawn("", opts, udpp.options.Handler, optsHandler)
   212  	if err != nil {
   213  		lib.Warning("[gen.UDP] can not start UDPHandler: %s", err)
   214  		return nil
   215  	}
   216  	return p
   217  }
   218  
   219  func (udpp *UDPProcess) serve() {
   220  	var handlerProcess Process
   221  	var handlerProcessID int
   222  	var packet interface{}
   223  	defer udpp.packetConn.Close()
   224  
   225  	writer := &writer{
   226  		pconn: udpp.packetConn,
   227  	}
   228  
   229  	ctx := udpp.Context()
   230  	deadlineTimeout := time.Second * time.Duration(udpp.options.DeadlineTimeout)
   231  
   232  	l := uint64(udpp.options.NumHandlers)
   233  	// make round robin using the counter value
   234  	cnt := atomic.AddUint64(&udpp.counter, 1)
   235  	// choose process as a handler for the packet received on this connection
   236  	handlerProcessID = int(cnt % l)
   237  	handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&udpp.pool[handlerProcessID]))))
   238  
   239  nextPacket:
   240  	for {
   241  		if ctx.Err() != nil {
   242  			return
   243  		}
   244  		deadline := false
   245  		if err := udpp.packetConn.SetReadDeadline(time.Now().Add(deadlineTimeout)); err == nil {
   246  			deadline = true
   247  		}
   248  		buf := lib.TakeBuffer()
   249  		buf.Allocate(udpp.options.MaxPacketSize)
   250  		n, a, err := udpp.packetConn.ReadFrom(buf.B)
   251  		if n == 0 {
   252  			if err, ok := err.(net.Error); deadline && ok && err.Timeout() {
   253  				packet = messageUDPHandlerTimeout{}
   254  				break
   255  			}
   256  			// stop serving and close this socket
   257  			return
   258  		}
   259  		if err != nil {
   260  			lib.Warning("[gen.UDP] got error on receiving packet from %q: %s", a, err)
   261  		}
   262  
   263  		writer.addr = a
   264  		packet = messageUDPHandlerPacket{
   265  			data: buf,
   266  			packet: UDPPacket{
   267  				Addr:   a,
   268  				Socket: writer,
   269  			},
   270  			n: n,
   271  		}
   272  		break
   273  	}
   274  
   275  retry:
   276  	for a := uint64(0); a < l; a++ {
   277  		if ctx.Err() != nil {
   278  			return
   279  		}
   280  
   281  		err := udpp.Cast(handlerProcess.Self(), packet)
   282  		switch err {
   283  		case nil:
   284  			break
   285  		case lib.ErrProcessUnknown:
   286  			if handlerProcessID == -1 {
   287  				// it was an extra handler do not restart. try to use the existing one
   288  				cnt = atomic.AddUint64(&udpp.counter, 1)
   289  				handlerProcessID = int(cnt % l)
   290  				handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&udpp.pool[handlerProcessID]))))
   291  				goto retry
   292  			}
   293  
   294  			// respawn terminated process
   295  			handlerProcess = udpp.startHandler(handlerProcessID, udpp.options.IdleTimeout)
   296  			atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&udpp.pool[handlerProcessID])), unsafe.Pointer(&handlerProcess))
   297  			continue
   298  
   299  		case lib.ErrProcessBusy:
   300  			handlerProcessID = int((a + cnt) % l)
   301  			handlerProcess = *(*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&udpp.pool[handlerProcessID]))))
   302  			continue
   303  		default:
   304  			lib.Warning("[gen.UDP] error on handling packet %#v: %s", packet, err)
   305  		}
   306  		goto nextPacket
   307  	}
   308  }
   309  
   310  type writer struct {
   311  	pconn net.PacketConn
   312  	addr  net.Addr
   313  }
   314  
   315  func (w *writer) Write(data []byte) (int, error) {
   316  	return w.pconn.WriteTo(data, w.addr)
   317  }