Example #1
0
        /// <summary>
        /// Publishes a message.
        /// </summary>
        /// <param name="contract">The contract</param>
        /// <param name="message">The message to publish.</param>
        /// <param name="channelHash">The hashed channel id.</param>
        /// <param name="channelName">The full name of the channel.</param>
        /// <param name="ttl">The time-to-live value for the message.</param>
        public static void Publish(EmitterContract contract, uint channelHash, string channelName, ArraySegment <byte> message, int ttl = EmitterConst.Transient)
        {
            try
            {
                // Make sure the TTL is within limits and max is 30 days.
                if (ttl < 0)
                {
                    ttl = EmitterConst.Transient;
                }
                if (ttl > 2592000)
                {
                    ttl = 2592000;
                }

                var contractId = contract.Oid;
                var cid        = ((long)contractId << 32) + channelHash;

                // Increment the counter
                Service.MessageReceived?.Invoke(contract, channelName, message.Count);

                // The SSID
                var ssid = EmitterChannel.Ssid(contractId, channelName);

                // Get the subscriptions
                var subs = Subscription.Match(ssid);
                if (subs != null)
                {
                    // Fast-Path: check local subscribers and send it to them
                    ForwardToClients(contractId, channelName, message, subs);

                    // Publish into the messaging cluster once
                    ForwardToServers(contractId, channelName, message, subs);
                }

                // Only call into the storage service if necessary
                if (ttl > 0 && Services.Storage != null)
                {
                    // If we have a storage service, store the message
                    Services.Storage.AppendAsync(contractId, ssid, ttl, message);
                }
            }
            catch (Exception ex)
            {
                Service.Logger.Log(LogLevel.Warning, "Publish failed. Reason: " + ex.Message);
                //Service.Logger.Log(ex);
                return;
            }
        }
Example #2
0
        /// <summary>
        /// Publishes the status to the cluster.
        /// </summary>
        public void Publish()
        {
            // Enqueue into samplers
            SamplerMPSIn.CumulativeDelta(Service.Monitoring.MessagesIncoming);
            SamplerMPSOut.CumulativeDelta(Service.Monitoring.MessagesOutgoing);

            // Push the time and static values
            this.Time    = DateTime.UtcNow;
            this.Node    = EmitterStatus.NodeIdentifier.ToString();
            this.Host    = EmitterStatus.Address;
            this.Machine = EmitterStatus.MachineName;
            this.CPU     = Math.Round(NetStat.AverageCpuUsage.Value, 2);
            this.Memory  = Math.Round(NetStat.AverageWorkingSet.Value, 2);
            this.Uptime  = NetStat.Uptime;

            // Set the network info
            this.Network.Connections        = Service.Clients.Count;
            this.Network.AveragePPSIncoming = Math.Round(NetStat.AveragePPSIncoming.Value, 2);
            this.Network.AveragePPSOutgoing = Math.Round(NetStat.AveragePPSOutgoing.Value, 2);
            this.Network.AverageBPSIncoming = Math.Round(NetStat.AverageBPSIncoming.Value, 2);
            this.Network.AverageBPSOutgoing = Math.Round(NetStat.AverageBPSOutgoing.Value, 2);
            this.Network.AverageMPSIncoming = Math.Round(SamplerMPSIn.Sum(), 2);
            this.Network.AverageMPSOutgoing = Math.Round(SamplerMPSOut.Sum(), 2);
            this.Network.Compression        = Math.Round(NetStat.Compression.Value, 2);

            // Create the message
            var message = Encoding.UTF8.GetBytes(
                JsonConvert.SerializeObject(this)
                ).AsSegment();

            // Prepare options
            var contract = SecurityLicense.Current.Contract;
            var channel  = "cluster/" + this.Host + "/";

            // Get the channel hash for query
            EmitterChannel info;

            if (!EmitterChannel.TryParse(channel, false, out info))
            {
                return;
            }

            // Publish the message in the cluster, don't need to store it as it might slow everything down
            Dispatcher.Publish(contract, info.Target, channel, message, 60);
        }
Example #3
0
        /// <summary>
        /// Forwards a message to a set of subscriptions
        /// </summary>
        internal static void ForwardToClients(int contractId, string channel, ArraySegment <byte> message)
        {
            // Separator must be added
            channel += EmitterConst.Separator;

            // Create an SSID
            var ssid = EmitterChannel.Ssid(contractId, channel);

            // Get the matching subscriptions
            var subs = Subscription.Match(ssid);

            if (subs == null)
            {
                return;
            }

            // Forward to the clients
            ForwardToClients(contractId, channel, message, subs);
        }
Example #4
0
        /// <summary>
        /// Unregisters a subscription from the current registry.
        /// </summary>
        /// <param name="client">The client to unsubscribe.</param>
        /// <param name="contract">The contract.</param>
        /// <param name="channel"></param>
        /// <returns>Whether the subscription was removed or not.</returns>
        public static bool Unregister(IMqttSender client, int contract, string channel, SubscriptionInterest interest, out Subscription subs)
        {
            // Make the subscription id
            var ssid = EmitterChannel.Ssid(contract, channel);

            // Get the subscription first
            if (!Index.TryGetValue(ssid, 0, out subs))
            {
                return(false);
            }

            // Validate, only the correct contract can unsubscribe
            if (subs.ContractKey != contract)
            {
                return(false);
            }

            // Unregister a client
            subs.Unsubscribe(client, interest);
            return(true);
        }
Example #5
0
        /// <summary>
        /// Disposes the subscription.
        /// </summary>
        public void Dispose()
        {
            try
            {
                // Unbind events
                if (this.Presence != null)
                {
                    this.Presence.Change -= this.OnPresenceChange;
                }

                // Get the ssid
                var ssid = EmitterChannel.Ssid(this.ContractKey, this.Channel);

                // Remove the subscription from the registry
                Subscription removed;
                Index.TryRemove(ssid, 0, out removed);
            }
            catch (Exception ex)
            {
                Service.Logger.Log(ex);
            }
        }
Example #6
0
        /// <summary>
        /// Unregisters a subscription from the current registry.
        /// </summary>
        /// <param name="server">The server to unsubscribe.</param>
        /// <param name="contract">The contract.</param>
        /// <param name="channel"></param>
        /// <returns>Whether the subscription was removed or not.</returns>
        public static bool Unregister(IServer server, int contract, string channel)
        {
            // Make the subscription id
            var ssid = EmitterChannel.Ssid(contract, channel);

            // Get the subscription first
            Subscription subs;

            if (!Index.TryGetValue(ssid, 0, out subs))
            {
                return(false);
            }

            // Validate, only the correct contract can unsubscribe
            if (subs.ContractKey != contract)
            {
                return(false);
            }

            // Unregister a server
            subs.Unsubscribe(server);
            return(false);
        }
Example #7
0
        /// <summary>
        /// Attempts to parse the channel.
        /// </summary>
        /// <param name="channel">The channel string to parse.</param>
        /// <param name="info">The channel information parsed.</param>
        /// <param name="withKey">Whether we should parse the key first or not.</param>
        /// <returns>Whether we parsed or not.</returns>
        public static unsafe bool TryParse(string channel, bool withKey, out EmitterChannel info)
        {
            // The result
            info        = new EmitterChannel();
            info.Type   = ChannelType.Invalid;
            info.Target = 0;

            // Variables and state
            char   symbol;
            string key    = null;
            int    begin  = 0;
            var    last   = false;
            var    state  = withKey ? ParseState.ReadingKey : ParseState.ReadingChannel;
            var    length = channel.Length;

            // Pin the string
            fixed(char *pChar = channel)
            {
                // Start the single-pass parsing
                for (int i = 0; i < length; ++i)
                {
                    // Is this the last character?
                    last   = (i + 1 == length);
                    symbol = *(pChar + i);
                    switch (state)
                    {
                    // We're trying to read the provided API key, this should be the 32-character long
                    // key or 'emitter' string for custom API requests.
                    case ParseState.ReadingKey:
                        // We only need to wait until the '/' sign
                        if (symbol != EmitterConst.Separator)
                        {
                            break;
                        }

                        // Copy the key
                        info.Key = new string(pChar, 0, i);
                        begin    = i + 1;

                        // Change the state and continue
                        state = ParseState.ReadingChannel;
                        continue;

                    // We're trying to read and validate the channel and infer the type of the channel.
                    case ParseState.ReadingChannel:

                        // If we're reading the first separator and haven't set the channel hash yet, we should
                        // compute the Murmur32 hash on our root.
                        if (info.Target == 0 && (last || symbol == EmitterConst.Separator))
                        {
                            info.Target = GetHash(pChar + begin, i - begin + (symbol == EmitterConst.Separator ? 0 : 1));
                        }

                        // If this symbol is a wildcard symbol
                        if (symbol == '+' || symbol == '*')
                        {
                            // Check if previous character is a '/' and whether we have another '/', '?' or nothing after.
                            if (*(pChar + i - 1) == EmitterConst.Separator &&
                                (*(pChar + i + 1) == EmitterConst.Separator || *(pChar + i + 1) == '?' || last))
                            {
                                // This is a valid wildcard
                                info.Type = ChannelType.Wildcard;
                                continue;
                            }

                            // Channel is invalid, we should stop parsing
                            return(false);
                        }
                        // If it's the last character in the string, assign the whole thing to the channel. Similarly,
                        // if this is the '?' sign that means we have finished parsing the channel and can assign it.
                        else if (last || symbol == '?')
                        {
                            // We can now set the channel string
                            info.Channel = new string(pChar, begin, i - begin + (symbol == '?' ? 0 : 1));
                            begin        = i + 1;

                            // Make sure channel ends with trailing slash
                            if (!(info.Channel[info.Channel.Length - 1] == EmitterConst.Separator))
                            {
                                info.Channel += EmitterConst.Separator;
                            }

                            // If we haven't detected that the channel is a wildcard, this means this is the
                            // static channel or root. Set the type now.
                            if (info.Type != ChannelType.Wildcard)
                            {
                                info.Type = ChannelType.Static;
                            }

                            // We need to create the options dictionary, since we do have options
                            if (symbol == '?')
                            {
                                info.Options = new Dictionary <string, string>();
                            }

                            // Change the state and continue
                            state = ParseState.ReadingOptionKey;
                            continue;
                        }
                        // Is this a valid character?
                        else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) ||
                                 (symbol == Code.DOT) ||
                                 (symbol == Code.MINUS) ||
                                 (symbol == Code.COLON) ||
                                 (symbol == EmitterConst.Separator) ||
                                 (symbol >= Code.A && symbol <= Code.Z) ||
                                 (symbol >= Code.a && symbol <= Code.z))
                        {
                            // That's ok, continue
                            continue;
                        }
                        else
                        {
                            // Channel is invalid, we should stop parsing
                            return(false);
                        }

                    // We're now reading the options, using the query-string format. This is a simple set of key/value pairs
                    // separated from the channel by '?', from each other by '&' and key from value by '='.
                    case ParseState.ReadingOptionKey:

                        // This is the case where we do not have a value, we assign an empty value
                        if (last || symbol == '=')
                        {
                            // Get the key
                            key = new string(pChar, begin, i - begin + (symbol == '=' ? 0 : 1));

                            // Change the state and continue
                            begin = i + 1;
                            state = ParseState.ReadingOptionValue;
                            continue;
                        }
                        // If we don't have the value
                        else if (last || symbol == '&')
                        {
                            // Write the key we have for this value and the value itself
                            key = new string(pChar, begin, i - begin + (symbol == '&' ? 0 : 1));
                            if (!info.Options.ContainsKey(key))
                            {
                                info.Options.Add(key, string.Empty);
                                key = null;
                            }

                            // Keep the state and continue
                            begin = i + 1;
                            continue;
                        }
                        // Is this a valid character?
                        else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) ||
                                 (symbol == Code.MINUS) ||
                                 (symbol >= Code.A && symbol <= Code.Z) ||
                                 (symbol >= Code.a && symbol <= Code.z))
                        {
                            // That's ok, continue
                            continue;
                        }
                        else
                        {
                            // Channel is invalid, we should stop parsing
                            return(false);
                        }

                    // Similarly, we parse the value of the option. In some cases, options could have no value and serve as
                    // simple flags (ie: true if present, false if not present).
                    case ParseState.ReadingOptionValue:

                        // We've finished reading the value, put it to the options dictionary now
                        if (last || symbol == '&')
                        {
                            // Write the key we have for this value and the value itself
                            if (!info.Options.ContainsKey(key))
                            {
                                info.Options.Add(key, new string(pChar, begin, i - begin + (symbol == '&' ? 0 : 1)));
                                key = null;
                            }

                            // Change the state and continue
                            begin = i + 1;
                            state = ParseState.ReadingOptionKey;
                            continue;
                        }
                        // Is this a valid character?
                        else if ((symbol >= Code.DIGIT_0 && symbol <= Code.DIGIT_9) ||
                                 (symbol == Code.MINUS) ||
                                 (symbol >= Code.A && symbol <= Code.Z) ||
                                 (symbol >= Code.a && symbol <= Code.z))
                        {
                            // That's ok, continue
                            continue;
                        }
                        else
                        {
                            // Channel is invalid, we should stop parsing
                            return(false);
                        }
                    }
                }

                // We're done parsing, return the structure
                return(true);
            }
        }
Example #8
0
 /// <summary>
 /// Adds or updates the subscription.
 /// </summary>
 /// <param name="channel">The channel for the subscription to find.</param>
 /// <param name="contract">The channel for the subscription to find.</param>
 /// <param name="addFunc">The add function to execute.</param>
 /// <param name="updateFunc">The update function to execute.</param>
 /// <returns></returns>
 public Subscription AddOrUpdate(int contract, string channel, Func <Subscription> addFunc, Func <Subscription, Subscription> updateFunc)
 {
     return(this.AddOrUpdate(EmitterChannel.Ssid(contract, channel), 0, addFunc, updateFunc));
 }
Example #9
0
 /// <summary>
 /// Attempts to fetch a subscription from the trie.
 /// </summary>
 /// <param name="sub">The subscription retrieved.</param>
 /// <param name="channel">The channel for the subscription to find.</param>
 /// <param name="contract">The channel for the subscription to find.</param>
 /// <returns>Whether the subscription was found or not.</returns>
 public bool TryGetValue(int contract, string channel, out Subscription sub)
 {
     return(this.TryGetValue(EmitterChannel.Ssid(contract, channel), 0, out sub));
 }
Example #10
0
        /// <summary>
        /// Attempts to remove a subscription from the trie.
        /// </summary>
        /// <param name="sub">The subscription to remove.</param>
        /// <returns>Whether the subscription was removed or not.</returns>
        public bool TryRemove(Subscription sub)
        {
            Subscription removed;

            return(this.TryRemove(EmitterChannel.Ssid(sub.ContractKey, sub.Channel), 0, out removed));
        }
Example #11
0
 /// <summary>
 /// Attempts to add a subscription to the trie.
 /// </summary>
 /// <param name="sub">The subscription to add.</param>
 /// <returns>Whether the subscription was added or not.</returns>
 public bool TryAdd(Subscription sub)
 {
     return(this.TryAdd(EmitterChannel.Ssid(sub.ContractKey, sub.Channel), sub));
 }