2026-04-15 15:24:36 +08:00
|
|
|
package notify
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"b612.me/stario"
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func batchedControlEnvelope(env Envelope) bool {
|
|
|
|
|
switch env.Kind {
|
|
|
|
|
case EnvelopeSignal, EnvelopeSignalAck:
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeDeadlineFromTimeout(timeout time.Duration) time.Time {
|
|
|
|
|
if timeout <= 0 {
|
|
|
|
|
return time.Time{}
|
|
|
|
|
}
|
|
|
|
|
return time.Now().Add(timeout)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) sendHeartbeat() error {
|
|
|
|
|
_, err := c.sendWait(TransferMsg{
|
|
|
|
|
ID: 10000,
|
|
|
|
|
Key: "heartbeat",
|
|
|
|
|
Value: nil,
|
|
|
|
|
Type: MSG_SYS_WAIT,
|
|
|
|
|
}, time.Second*5)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleHeartbeatResult(err error, failedCount int) (int, bool) {
|
|
|
|
|
return c.handleHeartbeatResultWithSession(c.currentClientSessionEpoch(), err, failedCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleHeartbeatResultWithSession(epoch uint64, err error, failedCount int) (int, bool) {
|
|
|
|
|
if err == nil {
|
|
|
|
|
c.lastHeartbeat = time.Now().Unix()
|
|
|
|
|
return 0, false
|
|
|
|
|
}
|
|
|
|
|
if c.debugMode {
|
|
|
|
|
fmt.Println("failed to recv heartbeat,timeout!")
|
|
|
|
|
}
|
|
|
|
|
failedCount++
|
|
|
|
|
if failedCount < 3 {
|
|
|
|
|
return failedCount, false
|
|
|
|
|
}
|
|
|
|
|
if c.debugMode {
|
|
|
|
|
fmt.Println("heatbeat failed more than 3 times,stop client")
|
|
|
|
|
}
|
|
|
|
|
if !c.stopClientSessionIfCurrent(epoch, "heartbeat failed more than 3 times", errors.New("heartbeat failed more than 3 times")) {
|
|
|
|
|
return failedCount, true
|
|
|
|
|
}
|
|
|
|
|
return failedCount, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) readFromTransport() (int, []byte, error) {
|
|
|
|
|
return c.readFromTransportBinding(c.clientTransportBindingSnapshot())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) readFromTransportConn(conn net.Conn) (int, []byte, error) {
|
|
|
|
|
return c.readFromTransportBinding(newTransportBinding(conn, nil))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) readFromTransportBinding(binding *transportBinding) (int, []byte, error) {
|
|
|
|
|
return c.readFromTransportBindingWithBuffer(binding, streamReadBuffer())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) readFromTransportBindingWithBuffer(binding *transportBinding, data []byte) (int, []byte, error) {
|
|
|
|
|
if len(data) == 0 {
|
|
|
|
|
data = streamReadBuffer()
|
|
|
|
|
}
|
|
|
|
|
if binding == nil {
|
|
|
|
|
return 0, data, net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
conn := binding.connSnapshot()
|
|
|
|
|
if conn == nil {
|
|
|
|
|
return 0, data, net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
if c.maxReadTimeout.Seconds() != 0 {
|
|
|
|
|
_ = conn.SetReadDeadline(time.Now().Add(c.maxReadTimeout))
|
|
|
|
|
}
|
|
|
|
|
readNum, err := conn.Read(data)
|
|
|
|
|
return readNum, data, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportReadResult(readNum int, data []byte, err error) bool {
|
|
|
|
|
return c.handleTransportReadResultWithSession(c.clientStopContextSnapshot(), c.clientTransportConnSnapshot(), c.clientQueueSnapshot(), readNum, data, err, c.currentClientSessionEpoch())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportReadResultWithSession(stopCtx context.Context, conn net.Conn, queue *stario.StarQueue, readNum int, data []byte, err error, epoch uint64) bool {
|
|
|
|
|
return c.handleTransportReadResultWithSessionDispatcher(stopCtx, conn, queue, readNum, data, err, epoch, c.clientInboundDispatcherSnapshot())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportReadResultWithSessionDispatcher(stopCtx context.Context, conn net.Conn, queue *stario.StarQueue, readNum int, data []byte, err error, epoch uint64, dispatcher *inboundDispatcher) bool {
|
2026-09-23 15:33:17 +08:00
|
|
|
route := c.clientSessionRouteSnapshot()
|
|
|
|
|
if route.binding == nil || route.binding.connSnapshot() != conn || route.binding.queueSnapshot() != queue || route.epoch != epoch {
|
|
|
|
|
route = clientSessionRoute{binding: newTransportBinding(conn, queue), epoch: epoch, sessionStopCtx: stopCtx, transportStopCtx: stopCtx}
|
|
|
|
|
}
|
|
|
|
|
return c.handleTransportReadResultAtRoute(stopCtx, route, readNum, data, err, dispatcher)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportReadResultAtRoute(stopCtx context.Context, route clientSessionRoute, readNum int, data []byte, err error, dispatcher *inboundDispatcher) bool {
|
|
|
|
|
binding := route.binding
|
|
|
|
|
queue := binding.queueSnapshot()
|
2026-04-15 15:24:36 +08:00
|
|
|
if err == os.ErrDeadlineExceeded {
|
|
|
|
|
if readNum != 0 && queue != nil {
|
2026-09-23 15:33:17 +08:00
|
|
|
if !c.pushMessageFastAtRoute(route, queue, data[:readNum], dispatcher) {
|
|
|
|
|
queue.ParseMessage(data[:readNum], route.inboundQueueKey())
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
select {
|
|
|
|
|
case <-sessionStopChan(stopCtx):
|
|
|
|
|
c.closeClientTransportBinding(binding)
|
|
|
|
|
return false
|
|
|
|
|
default:
|
|
|
|
|
}
|
2026-09-23 15:33:17 +08:00
|
|
|
// An expected shutdown closes the socket, which surfaces here as a
|
|
|
|
|
// read on a closed connection. Only report reads that happen while the
|
|
|
|
|
// session is still supposed to be running.
|
|
|
|
|
if c.showError || c.debugMode {
|
|
|
|
|
fmt.Println("client read error", err)
|
|
|
|
|
}
|
|
|
|
|
c.stopClientSessionIfCurrent(route.epoch, "client read error", err)
|
2026-04-15 15:24:36 +08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if queue != nil {
|
2026-09-23 15:33:17 +08:00
|
|
|
if !c.pushMessageFastAtRoute(route, queue, data[:readNum], dispatcher) {
|
|
|
|
|
queue.ParseMessage(data[:readNum], route.inboundQueueKey())
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 16:05:57 +08:00
|
|
|
func (c *ClientCommon) readTransportPayloadPooled(conn net.Conn, reader *stario.FrameReader) ([]byte, func(), error) {
|
|
|
|
|
if reader == nil {
|
|
|
|
|
return nil, nil, net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
if conn == nil {
|
|
|
|
|
return nil, nil, net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
if c.maxReadTimeout.Seconds() != 0 {
|
|
|
|
|
_ = conn.SetReadDeadline(time.Now().Add(c.maxReadTimeout))
|
|
|
|
|
}
|
|
|
|
|
return reader.NextPooled()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportPayloadReadResultWithSession(stopCtx context.Context, binding *transportBinding, payload []byte, release func(), err error, epoch uint64, dispatcher *inboundDispatcher) bool {
|
2026-09-23 15:33:17 +08:00
|
|
|
route := c.clientSessionRouteSnapshot()
|
|
|
|
|
if route.binding != binding || route.epoch != epoch {
|
|
|
|
|
route = clientSessionRoute{binding: binding, epoch: epoch, sessionStopCtx: stopCtx, transportStopCtx: stopCtx}
|
|
|
|
|
}
|
|
|
|
|
return c.handleTransportPayloadReadResultAtRoute(stopCtx, route, payload, release, err, dispatcher)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) handleTransportPayloadReadResultAtRoute(stopCtx context.Context, route clientSessionRoute, payload []byte, release func(), err error, dispatcher *inboundDispatcher) bool {
|
|
|
|
|
binding := route.binding
|
2026-04-18 16:05:57 +08:00
|
|
|
if err == os.ErrDeadlineExceeded {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
if release != nil {
|
|
|
|
|
release()
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case <-sessionStopChan(stopCtx):
|
|
|
|
|
c.closeClientTransportBinding(binding)
|
|
|
|
|
return false
|
|
|
|
|
default:
|
|
|
|
|
}
|
2026-09-23 15:33:17 +08:00
|
|
|
// See handleTransportReadResultAtRoute: shutdown reads are expected.
|
|
|
|
|
if c.showError || c.debugMode {
|
|
|
|
|
fmt.Println("client read error", err)
|
|
|
|
|
}
|
|
|
|
|
c.stopClientSessionIfCurrent(route.epoch, "client read error", err)
|
2026-04-18 16:05:57 +08:00
|
|
|
return false
|
|
|
|
|
}
|
2026-09-23 15:33:17 +08:00
|
|
|
c.dispatchTransportPayloadFastAtRoute(route, payload, release, dispatcher)
|
2026-04-18 16:05:57 +08:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) dispatchTransportPayloadFast(payload []byte, release func(), dispatcher *inboundDispatcher) {
|
2026-09-23 15:33:17 +08:00
|
|
|
c.dispatchTransportPayloadFastAtRoute(c.clientSessionRouteSnapshot(), payload, release, dispatcher)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) dispatchTransportPayloadFastAtRoute(route clientSessionRoute, payload []byte, release func(), dispatcher *inboundDispatcher) {
|
2026-04-18 16:05:57 +08:00
|
|
|
if len(payload) == 0 {
|
|
|
|
|
if release != nil {
|
|
|
|
|
release()
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-20 16:35:44 +08:00
|
|
|
plain, plainRelease, err := c.decryptTransportPayloadPooled(payload, release)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if c.showError || c.debugMode {
|
|
|
|
|
fmt.Println("client decode transport payload error", err)
|
2026-04-18 16:05:57 +08:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-09-23 15:33:17 +08:00
|
|
|
if c.tryDispatchBorrowedTransportPlainAtRoute(route, plain, plainRelease) {
|
2026-04-20 16:35:44 +08:00
|
|
|
return
|
2026-04-18 16:05:57 +08:00
|
|
|
}
|
|
|
|
|
if dispatcher == nil {
|
|
|
|
|
now := time.Now()
|
2026-09-23 15:33:17 +08:00
|
|
|
err := c.dispatchInboundTransportPlainAtRoute(route, plain, now)
|
2026-04-20 16:35:44 +08:00
|
|
|
if plainRelease != nil {
|
|
|
|
|
plainRelease()
|
|
|
|
|
}
|
|
|
|
|
if err != nil && (c.showError || c.debugMode) {
|
2026-04-18 16:05:57 +08:00
|
|
|
fmt.Println("client decode envelope error", err)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-04-20 16:35:44 +08:00
|
|
|
owned := plain
|
|
|
|
|
if plainRelease != nil {
|
|
|
|
|
owned = append([]byte(nil), plain...)
|
|
|
|
|
plainRelease()
|
|
|
|
|
}
|
2026-04-18 16:05:57 +08:00
|
|
|
c.wg.Add(1)
|
2026-09-23 15:33:17 +08:00
|
|
|
if !dispatcher.DispatchSized(clientInboundDispatchSource(), len(owned), func() {
|
2026-04-18 16:05:57 +08:00
|
|
|
defer c.wg.Done()
|
|
|
|
|
now := time.Now()
|
2026-09-23 15:33:17 +08:00
|
|
|
if err := c.dispatchInboundTransportPlainAtRoute(route, owned, now); err != nil && (c.showError || c.debugMode) {
|
2026-04-18 16:05:57 +08:00
|
|
|
fmt.Println("client decode envelope error", err)
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
c.wg.Done()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 15:24:36 +08:00
|
|
|
func (c *ClientCommon) pushMessageFast(queue *stario.StarQueue, data []byte, dispatcher *inboundDispatcher) bool {
|
2026-09-23 15:33:17 +08:00
|
|
|
return c.pushMessageFastAtRoute(c.clientSessionRouteSnapshot(), queue, data, dispatcher)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) pushMessageFastAtRoute(route clientSessionRoute, queue *stario.StarQueue, data []byte, dispatcher *inboundDispatcher) bool {
|
2026-04-15 15:24:36 +08:00
|
|
|
if queue == nil || dispatcher == nil || len(data) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-09-23 15:33:17 +08:00
|
|
|
if err := queue.ParseMessageView(data, route.inboundQueueKey(), func(frame stario.FrameView) error {
|
|
|
|
|
c.dispatchTransportPayloadFastAtRoute(route, frame.Payload, nil, dispatcher)
|
2026-04-15 15:24:36 +08:00
|
|
|
return nil
|
|
|
|
|
}); err != nil && (c.showError || c.debugMode) {
|
|
|
|
|
fmt.Println("client parse inbound frame error", err)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writeToTransport(data []byte) error {
|
|
|
|
|
binding := c.clientTransportBindingSnapshot()
|
|
|
|
|
if binding == nil {
|
|
|
|
|
return net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
return binding.withConnWriteLock(func(conn net.Conn) error {
|
|
|
|
|
if c.maxWriteTimeout.Seconds() != 0 {
|
|
|
|
|
_ = conn.SetWriteDeadline(time.Now().Add(c.maxWriteTimeout))
|
|
|
|
|
}
|
|
|
|
|
return writeFullToConnUnlocked(conn, data)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writePayloadToTransport(payload []byte) error {
|
2026-08-14 10:16:37 +08:00
|
|
|
return c.writePayloadToTransportContext(context.Background(), payload)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writePayloadToTransportContext(ctx context.Context, payload []byte) error {
|
|
|
|
|
return c.writePayloadToTransportContextTimeout(ctx, payload, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writePayloadToTransportContextTimeout(ctx context.Context, payload []byte, writeTimeout time.Duration) error {
|
2026-04-15 15:24:36 +08:00
|
|
|
binding := c.clientTransportBindingSnapshot()
|
2026-09-23 15:33:17 +08:00
|
|
|
return c.writePayloadToTransportBindingContextTimeout(ctx, binding, payload, writeTimeout)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writePayloadToTransportBindingContextTimeout(ctx context.Context, binding *transportBinding, payload []byte, writeTimeout time.Duration) error {
|
2026-04-15 15:24:36 +08:00
|
|
|
if binding == nil {
|
|
|
|
|
return net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
queue := binding.queueSnapshot()
|
|
|
|
|
if queue == nil {
|
|
|
|
|
return errClientSessionQueueUnavailable
|
|
|
|
|
}
|
2026-08-14 10:16:37 +08:00
|
|
|
writeTimeout = shorterPositiveDuration(c.maxWriteTimeoutSnapshot(), writeTimeout)
|
|
|
|
|
lockAcquired, err := binding.withConnWriteLockContextStopTimeout(ctx, nil, writeTimeout, func(conn net.Conn) error {
|
2026-04-15 15:24:36 +08:00
|
|
|
return writeFramedPayloadUnlocked(conn, queue, payload)
|
|
|
|
|
})
|
2026-08-14 10:16:37 +08:00
|
|
|
if lockAcquired && err != nil {
|
|
|
|
|
if conn := binding.connSnapshot(); conn != nil && !isPacketTransportConn(conn) {
|
|
|
|
|
binding.closeConn()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writeControlPayloadToTransport(ctx context.Context, payload []byte, priority controlPriority) error {
|
|
|
|
|
return c.writeControlPayloadToTransportTimeout(ctx, payload, priority, 0)
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-14 10:16:37 +08:00
|
|
|
func (c *ClientCommon) writeControlPayloadToTransportTimeout(ctx context.Context, payload []byte, priority controlPriority, writeTimeout time.Duration) error {
|
2026-04-15 15:24:36 +08:00
|
|
|
binding := c.clientTransportBindingSnapshot()
|
2026-09-23 15:33:17 +08:00
|
|
|
return c.writeControlPayloadToTransportBindingTimeout(ctx, binding, payload, priority, writeTimeout)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *ClientCommon) writeControlPayloadToTransportBindingTimeout(ctx context.Context, binding *transportBinding, payload []byte, priority controlPriority, writeTimeout time.Duration) error {
|
2026-04-15 15:24:36 +08:00
|
|
|
if binding == nil {
|
|
|
|
|
return net.ErrClosed
|
|
|
|
|
}
|
|
|
|
|
queue := binding.queueSnapshot()
|
|
|
|
|
if queue == nil {
|
|
|
|
|
return errClientSessionQueueUnavailable
|
|
|
|
|
}
|
|
|
|
|
conn := binding.connSnapshot()
|
|
|
|
|
if conn == nil || isPacketTransportConn(conn) {
|
2026-09-23 15:33:17 +08:00
|
|
|
return c.writePayloadToTransportBindingContextTimeout(ctx, binding, payload, writeTimeout)
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|
|
|
|
|
sender := binding.controlBatchSenderSnapshot()
|
|
|
|
|
if sender == nil {
|
2026-09-23 15:33:17 +08:00
|
|
|
return c.writePayloadToTransportBindingContextTimeout(ctx, binding, payload, writeTimeout)
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|
2026-08-14 10:16:37 +08:00
|
|
|
return sender.submitContext(ctx, payload, shorterPositiveDuration(c.maxWriteTimeoutSnapshot(), writeTimeout), priority)
|
2026-04-15 15:24:36 +08:00
|
|
|
}
|