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 }