github.com/asynkron/protoactor-go@v0.0.0-20240308120642-ef91a6abee75/remote/endpoint_writer.go (about)

     1  package remote
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"log/slog"
     7  	"time"
     8  
     9  	"github.com/asynkron/protoactor-go/actor"
    10  	"golang.org/x/net/context"
    11  	"google.golang.org/grpc"
    12  	"google.golang.org/protobuf/proto"
    13  )
    14  
    15  func endpointWriterProducer(remote *Remote, address string, config *Config) actor.Producer {
    16  	return func() actor.Actor {
    17  		return &endpointWriter{
    18  			address: address,
    19  			config:  config,
    20  			remote:  remote,
    21  		}
    22  	}
    23  }
    24  
    25  type endpointWriter struct {
    26  	config  *Config
    27  	address string
    28  	conn    *grpc.ClientConn
    29  	stream  Remoting_ReceiveClient
    30  	remote  *Remote
    31  }
    32  
    33  type restartAfterConnectFailure struct {
    34  	err error
    35  }
    36  
    37  func (state *endpointWriter) initialize(ctx actor.Context) {
    38  	now := time.Now()
    39  
    40  	state.remote.Logger().Info("Started EndpointWriter. connecting", slog.String("address", state.address))
    41  
    42  	var err error
    43  
    44  	for i := 0; i < state.remote.config.MaxRetryCount; i++ {
    45  		err = state.initializeInternal()
    46  		if err != nil {
    47  			state.remote.Logger().Error("EndpointWriter failed to connect", slog.String("address", state.address), slog.Any("error", err), slog.Int("retry", i))
    48  			// Wait 2 seconds to restart and retry
    49  			// Replace with Exponential Backoff
    50  			time.Sleep(2 * time.Second)
    51  			continue
    52  		}
    53  
    54  		break
    55  	}
    56  
    57  	if err != nil {
    58  		terminated := &EndpointTerminatedEvent{
    59  			Address: state.address,
    60  		}
    61  		state.remote.actorSystem.EventStream.Publish(terminated)
    62  
    63  		return
    64  
    65  		//	plog.Error("EndpointWriter failed to connect", log.String("address", state.address), log.Error(err))
    66  
    67  		// Wait 2 seconds to restart and retry
    68  		// TODO: Replace with Exponential Backoff
    69  		// send this as a message to self - do not block the mailbox processing
    70  		// if in the meantime the actor is stopped (EndpointTerminated event), the message will be ignored (deadlettered)
    71  		// TODO: would it be a better idea to just publish EndpointTerminatedEvent here? to use the same path as when the connection is lost?
    72  		//	time.AfterFunc(2*time.Second, func() {
    73  		//		ctx.Send(ctx.Self(), &restartAfterConnectFailure{err})
    74  		//	})
    75  
    76  	}
    77  
    78  	state.remote.Logger().Info("EndpointWriter connected", slog.String("address", state.address), slog.Duration("cost", time.Since(now)))
    79  }
    80  
    81  func (state *endpointWriter) initializeInternal() error {
    82  	conn, err := grpc.Dial(state.address, state.config.DialOptions...)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	state.conn = conn
    87  	c := NewRemotingClient(conn)
    88  	stream, err := c.Receive(context.Background(), state.config.CallOptions...)
    89  	if err != nil {
    90  		state.remote.Logger().Error("EndpointWriter failed to create receive stream", slog.String("address", state.address), slog.Any("error", err))
    91  		return err
    92  	}
    93  	state.stream = stream
    94  
    95  	err = stream.Send(&RemoteMessage{
    96  		MessageType: &RemoteMessage_ConnectRequest{
    97  			ConnectRequest: &ConnectRequest{
    98  				ConnectionType: &ConnectRequest_ServerConnection{
    99  					ServerConnection: &ServerConnection{
   100  						SystemId: state.remote.actorSystem.ID,
   101  						Address:  state.remote.actorSystem.Address(),
   102  					},
   103  				},
   104  			},
   105  		},
   106  	})
   107  	if err != nil {
   108  		state.remote.Logger().Error("EndpointWriter failed to send connect request", slog.String("address", state.address), slog.Any("error", err))
   109  		return err
   110  	}
   111  
   112  	connection, err := stream.Recv()
   113  	if err != nil {
   114  		state.remote.Logger().Error("EndpointWriter failed to receive connect response", slog.String("address", state.address), slog.Any("error", err))
   115  		return err
   116  	}
   117  
   118  	switch connection.MessageType.(type) {
   119  	case *RemoteMessage_ConnectResponse:
   120  		state.remote.Logger().Debug("Received connect response", slog.String("fromAddress", state.address))
   121  		// TODO: handle blocked status received from remote server
   122  		break
   123  	default:
   124  		state.remote.Logger().Error("EndpointWriter got invalid connect response", slog.String("address", state.address), slog.Any("type", connection.MessageType))
   125  		return errors.New("invalid connect response")
   126  	}
   127  
   128  	go func() {
   129  		for {
   130  			_, err := stream.Recv()
   131  			switch {
   132  			case errors.Is(err, io.EOF):
   133  				state.remote.Logger().Debug("EndpointWriter stream completed", slog.String("address", state.address))
   134  				return
   135  			case err != nil:
   136  				state.remote.Logger().Error("EndpointWriter lost connection", slog.String("address", state.address), slog.Any("error", err))
   137  				terminated := &EndpointTerminatedEvent{
   138  					Address: state.address,
   139  				}
   140  				state.remote.actorSystem.EventStream.Publish(terminated)
   141  				return
   142  			default: // DisconnectRequest
   143  				state.remote.Logger().Info("EndpointWriter got DisconnectRequest form remote", slog.String("address", state.address))
   144  				terminated := &EndpointTerminatedEvent{
   145  					Address: state.address,
   146  				}
   147  				state.remote.actorSystem.EventStream.Publish(terminated)
   148  			}
   149  		}
   150  	}()
   151  
   152  	connected := &EndpointConnectedEvent{Address: state.address}
   153  	state.remote.actorSystem.EventStream.Publish(connected)
   154  	return nil
   155  }
   156  
   157  func (state *endpointWriter) sendEnvelopes(msg []interface{}, ctx actor.Context) {
   158  	envelopes := make([]*MessageEnvelope, 0)
   159  
   160  	// type name uniqueness map name string to type index
   161  	typeNames := make(map[string]int32)
   162  	typeNamesArr := make([]string, 0)
   163  
   164  	targetNames := make(map[string]int32)
   165  	targetNamesArr := make([]*actor.PID, 0)
   166  
   167  	senderNames := make(map[string]int32)
   168  	senderNamesArr := make([]*actor.PID, 0)
   169  
   170  	var (
   171  		header       *MessageHeader
   172  		typeID       int32
   173  		targetID     int32
   174  		senderID     int32
   175  		serializerID int32
   176  	)
   177  
   178  	for _, tmp := range msg {
   179  		switch unwrapped := tmp.(type) {
   180  		case *EndpointTerminatedEvent, EndpointTerminatedEvent:
   181  			state.remote.Logger().Debug("Handling array wrapped terminate event", slog.String("address", state.address), slog.Any("message", unwrapped))
   182  			ctx.Stop(ctx.Self())
   183  			return
   184  		}
   185  
   186  		rd, _ := tmp.(*remoteDeliver)
   187  
   188  		if state.stream == nil { // not connected yet since first connection attempt failed and we are waiting for the retry
   189  			if rd.sender != nil {
   190  				state.remote.actorSystem.Root.Send(rd.sender, &actor.DeadLetterResponse{Target: rd.target})
   191  			} else {
   192  				state.remote.actorSystem.EventStream.Publish(&actor.DeadLetterEvent{Message: rd.message, Sender: rd.sender, PID: rd.target})
   193  			}
   194  			continue
   195  		}
   196  
   197  		if rd.header == nil || rd.header.Length() == 0 {
   198  			header = nil
   199  		} else {
   200  			header = &MessageHeader{
   201  				HeaderData: rd.header.ToMap(),
   202  			}
   203  		}
   204  
   205  		// if the message can be translated to a serialization representation, we do this here
   206  		// this only apply to root level messages and never to nested child objects inside the message
   207  		message := rd.message
   208  		var err error
   209  		if v, ok := message.(RootSerializable); ok {
   210  			message, err = v.Serialize()
   211  			if err != nil {
   212  				state.remote.Logger().Error("EndpointWriter failed to serialize message", slog.String("address", state.address), slog.Any("error", err), slog.Any("message", v))
   213  				continue
   214  			}
   215  		}
   216  
   217  		bytes, typeName, err := Serialize(message, serializerID)
   218  		if err != nil {
   219  			state.remote.Logger().Error("EndpointWriter failed to serialize message", slog.String("address", state.address), slog.Any("error", err), slog.Any("message", message))
   220  			continue
   221  		}
   222  		typeID, typeNamesArr = addToLookup(typeNames, typeName, typeNamesArr)
   223  		targetID, targetNamesArr = addToTargetLookup(targetNames, rd.target, targetNamesArr)
   224  		targetRequestID := rd.target.RequestId
   225  
   226  		senderID, senderNamesArr = addToSenderLookup(senderNames, rd.sender, senderNamesArr)
   227  		senderRequestID := uint32(0)
   228  		if rd.sender != nil {
   229  			senderRequestID = rd.sender.RequestId
   230  		}
   231  
   232  		envelopes = append(envelopes, &MessageEnvelope{
   233  			MessageHeader:   header,
   234  			MessageData:     bytes,
   235  			Sender:          senderID,
   236  			Target:          targetID,
   237  			TypeId:          typeID,
   238  			SerializerId:    serializerID,
   239  			TargetRequestId: targetRequestID,
   240  			SenderRequestId: senderRequestID,
   241  		})
   242  	}
   243  
   244  	if len(envelopes) == 0 {
   245  		return
   246  	}
   247  
   248  	err := state.stream.Send(&RemoteMessage{
   249  		MessageType: &RemoteMessage_MessageBatch{
   250  			MessageBatch: &MessageBatch{
   251  				TypeNames: typeNamesArr,
   252  				Targets:   targetNamesArr,
   253  				Senders:   senderNamesArr,
   254  				Envelopes: envelopes,
   255  			},
   256  		},
   257  	})
   258  	if err != nil {
   259  		ctx.Stash()
   260  		state.remote.Logger().Debug("gRPC Failed to send", slog.String("address", state.address), slog.Any("error", err))
   261  		ctx.Stop(ctx.Self())
   262  	}
   263  }
   264  
   265  func addToLookup(m map[string]int32, name string, a []string) (int32, []string) {
   266  	max := int32(len(m))
   267  	id, ok := m[name]
   268  	if !ok {
   269  		m[name] = max
   270  		id = max
   271  		a = append(a, name)
   272  	}
   273  	return id, a
   274  }
   275  
   276  func addToTargetLookup(m map[string]int32, pid *actor.PID, arr []*actor.PID) (int32, []*actor.PID) {
   277  	max := int32(len(m))
   278  	key := pid.Address + "/" + pid.Id
   279  	id, ok := m[key]
   280  	if !ok {
   281  		c, _ := proto.Clone(pid).(*actor.PID)
   282  		c.RequestId = 0
   283  		m[key] = max
   284  		id = max
   285  		arr = append(arr, c)
   286  	}
   287  	return id, arr
   288  }
   289  
   290  func addToSenderLookup(m map[string]int32, pid *actor.PID, arr []*actor.PID) (int32, []*actor.PID) {
   291  	if pid == nil {
   292  		return 0, arr
   293  	}
   294  
   295  	max := int32(len(m))
   296  	key := pid.Address + "/" + pid.Id
   297  	id, ok := m[key]
   298  	if !ok {
   299  		c, _ := proto.Clone(pid).(*actor.PID)
   300  		c.RequestId = 0
   301  		m[key] = max
   302  		id = max
   303  		arr = append(arr, c)
   304  	}
   305  	return id + 1, arr
   306  }
   307  
   308  func (state *endpointWriter) Receive(ctx actor.Context) {
   309  	switch msg := ctx.Message().(type) {
   310  	case *actor.Started:
   311  		state.initialize(ctx)
   312  	case *actor.Stopped:
   313  		state.remote.Logger().Debug("EndpointWriter stopped", slog.String("address", state.address))
   314  		state.closeClientConn()
   315  	case *actor.Restarting:
   316  		state.remote.Logger().Debug("EndpointWriter restarting", slog.String("address", state.address))
   317  		state.closeClientConn()
   318  	case *EndpointTerminatedEvent:
   319  		state.remote.Logger().Info("EndpointWriter received EndpointTerminatedEvent, stopping", slog.String("address", state.address))
   320  		ctx.Stop(ctx.Self())
   321  	case *restartAfterConnectFailure:
   322  		state.remote.Logger().Debug("EndpointWriter initiating self-restart after failing to connect and a delay", slog.String("address", state.address))
   323  		panic(msg.err)
   324  	case []interface{}:
   325  		state.sendEnvelopes(msg, ctx)
   326  	case actor.SystemMessage, actor.AutoReceiveMessage:
   327  		// ignore
   328  	default:
   329  		state.remote.Logger().Error("EndpointWriter received unknown message", slog.String("address", state.address), slog.Any("message", msg))
   330  	}
   331  }
   332  
   333  func (state *endpointWriter) closeClientConn() {
   334  	state.remote.Logger().Info("EndpointWriter closing client connection", slog.String("address", state.address))
   335  	if state.stream != nil {
   336  		err := state.stream.CloseSend()
   337  		if err != nil {
   338  			state.remote.Logger().Error("EndpointWriter error when closing the stream", slog.Any("error", err))
   339  		}
   340  		state.stream = nil
   341  	}
   342  	if state.conn != nil {
   343  		err := state.conn.Close()
   344  		if err != nil {
   345  			state.remote.Logger().Error("EndpointWriter error when closing the client conn", slog.Any("error", err))
   346  		}
   347  		state.conn = nil
   348  	}
   349  }