/// <summary> /// Notifies of a 'members view' event. /// </summary> /// <param name="version">The version.</param> /// <param name="members">The members.</param> public async ValueTask <MembersUpdatedEventArgs> NotifyMembersView(int version, ICollection <MemberInfo> members) { // FIXME could we process two of these at a time? // get a new table var table = new MemberTable(version, members); // compute changes // count 1 for old members, 2 for new members, and then the result is // that 1=removed, 2=added, 3=unchanged // MemberInfo overrides GetHashCode and can be used as a key here var diff = new Dictionary <MemberInfo, int>(); if (_memberTable == null) { foreach (var m in table.Members.Values) { diff[m] = 2; } } else { foreach (var m in _memberTable.Members.Values) { diff[m] = 1; } foreach (var m in table.Members.Values) { if (diff.ContainsKey(m)) { diff[m] += 2; } else { diff[m] = 2; } } } // replace the table _memberTable = table; // notify the load balancer of the new list of members _loadBalancer.NotifyMembers(members.Select(x => x.Id)); // signal once if (Interlocked.CompareExchange(ref _firstMembersViewed, 1, 0) == 0) { _firstMembersView.Release(); } // process changes, gather events var added = new List <MemberInfo>(); var removed = new List <MemberInfo>(); foreach (var(member, status) in diff) { switch (status) { case 1: // old but not new = removed HConsole.WriteLine(this, $"Removed member {member.Id}"); removed.Add(member); if (_connections.TryGetValue(member.Id, out var client)) { await client.TerminateAsync().CfAwait(); // TODO: consider dying in the background? } break; case 2: // new but not old = added HConsole.WriteLine(this, $"Added member {member.Id}"); added.Add(member); break; case 3: // old and new = no change break; default: throw new NotSupportedException(); } } return(new MembersUpdatedEventArgs(added, removed, table.Members.Values)); }