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 }