public ReplicatorActor(ReplicaId myself, ReplicatorSettings settings) { this.myself = myself; this.settings = settings; replicatorRelativePath = Self.Path.ToStringWithoutAddress(); Receive <ClusterEvent.MemberUp>(up => { if (HasRole(up.Member) && up.Member.Address != cluster.SelfAddress) { // send invitation to a new member var path = up.Member.Address.ToString() + replicatorRelativePath; Context.ActorSelection(path).Tell(new Invitation(myself, Self)); } }); Receive <ClusterEvent.IMemberEvent>(_ => { /* ignore */ }); Receive <Broadcast <T> >(bcast => { var version = localVersion.Increment(myself); var versioned = new Versioned <T>(version, bcast.Message); var send = new Send <T>(myself, myself, versioned); if (log.IsInfoEnabled) { log.Info("Sending {0} to: {1}", send, string.Join(", ", members)); } foreach (var member in members) { member.Value.Forward(send); } var pendingAck = new PendingAck <T>(myself, versioned, DateTime.UtcNow, members.Keys.ToImmutableHashSet()); this.pendingAcks = pendingAcks.Add(pendingAck); this.localVersion = version; }); Receive <Send <T> >(send => { if (AlreadySeen(send.Versioned.Version)) { log.Info("Received duplicate message {0}", send); } else { var receivers = members.Remove(send.Origin); var forward = send.WithLastSeenBy(myself); if (log.IsInfoEnabled) { log.Info("Broadcasting message {0} to: {1}", forward, string.Join(", ", receivers.Values)); } foreach (var member in receivers.Values) { member.Forward(forward); } if (members.TryGetValue(send.LastSeenBy, out var sender)) { sender.Tell(new SendAck(myself, send.Versioned.Version), ActorRefs.NoSender); } var deliver = new Deliver <T>(send.Origin, send.Versioned); Self.Forward(deliver); this.pendingDelivery = pendingDelivery.Add(deliver); this.pendingAcks = pendingAcks.Add(new PendingAck <T>(myself, send.Versioned, DateTime.UtcNow, receivers.Keys.ToImmutableHashSet())); } }); Receive <SendAck>(ack => { log.Info("Received ACK from {0} (version: {1})", Sender, ack.Version); var pendingAck = this.pendingAcks.First(x => x.Versioned.Version == ack.Version); var membersLeft = pendingAck.Members.Remove(ack.ReplicaId); this.pendingAcks = pendingAcks.Remove(pendingAck); if (!membersLeft.IsEmpty) { this.pendingAcks = pendingAcks.Add(pendingAck.WithMembers(membersLeft)); } }); Receive <Deliver <T> >(deliver => { TryToCasuallyDeliver(deliver); remoteVersions = remoteVersions.SetItem(deliver.Origin, deliver.Versioned.Version); latestStableVersion = UpdateStableVersion(remoteVersions); }); Receive <Resend>(_ => { var now = DateTime.UtcNow; var builder = pendingAcks.ToBuilder(); foreach (var ack in pendingAcks) { if (now - ack.Timestamp > settings.RetryTimeout) { builder.Remove(ack); var send = new Send <T>(myself, myself, ack.Versioned); foreach (var replicaId in ack.Members) { if (members.TryGetValue(replicaId, out var member)) { member.Tell(send, ActorRefs.NoSender); } } builder.Add(ack.WithTimestamp(now)); } } pendingAcks = builder.ToImmutable(); }); Receive <Invitation>(invitation => { log.Info("Received invitation from {0} (ID: {1})", invitation.ReplicatorRef, invitation.ReplicaId); members = members.Add(invitation.ReplicaId, invitation.ReplicatorRef); Context.Watch(invitation.ReplicatorRef); }); Receive <StableReq>(sync => { var reply = sync.Versions.Where(ver => latestStableVersion >= ver).ToArray(); Sender.Tell(new StableRep(reply)); }); Receive <Subscribe <T> >(subscribe => { subscribers = subscribers.Add(subscribe.Ref); if (subscribe.Ack != null) { subscribe.Ref.Tell(subscribe.Ack); } }); Receive <Unsubscribe>(unsubscribe => { subscribers = subscribers.Remove(unsubscribe.Ref); if (unsubscribe.Ack != null) { unsubscribe.Ref.Tell(unsubscribe.Ack); } }); Receive <Terminated>(terminated => { subscribers = subscribers.Remove(terminated.ActorRef); var replicaId = members.FirstOrDefault(kv => Equals(kv.Value, terminated.ActorRef)); members = members.Remove(replicaId.Key); }); resendTask = Context.System.Scheduler .ScheduleTellOnceCancelable(settings.ResendInterval, Self, Resend.Instance, ActorRefs.NoSender); }
public ReplicatorActor(string replicaId, ReplicatorSettings settings) : this(MurmurHash.StringHash(replicaId), settings) { }
/// <summary> /// Creates a replicator flow that will be able to send messages using Tagged Reliable Casual Broadcast protocol. /// This flow will reliably deliver all input messages of type <typeparamref name="T"/> and produce a /// <see cref="Versioned{T}"/> events as the ones casually delivered to current replica. /// /// In order to behave reliably, this bidi flow requires to be set atop of eventsourced flow. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="streamId"></param> /// <param name="replicaId"></param> /// <param name="settings"></param> /// <returns></returns> public static BidiFlow <IOutgoing <T>, object, object, IIncoming <T>, NotUsed> ReliableCasualBroadcast <T>(string streamId, string replicaId, ReplicatorSettings settings = null) => throw new NotImplementedException();
public static Props Props(string replicaId, ReplicatorSettings settings) => Akka.Actor.Props.Create(() => new ReplicatorActor <T>(replicaId, settings)).WithDeploy(Deploy.Local);
public ReplicatorManager(ReplicatorSettings settings) { this.settings = settings; }