emitter-io / csharp

Client library for emitter.io compatible with .Net, .Net MicroFramework and WinRT
http://emitter.io
Eclipse Public License 1.0
29 stars 21 forks source link

ReverseTrie problem #19

Closed postacik closed 4 years ago

postacik commented 4 years ago
emitter.Subscribe("channel/", (chan, msg) => { Console.WriteLine("Handler1: " + Encoding.UTF8.GetString(msg)); });
emitter.Subscribe("channel/", (chan, msg) => { Console.WriteLine("Handler2: " + Encoding.UTF8.GetString(msg)); });

Hi, When the code above is executed and a message arrives on "channel/", only the second handler is fired.

Is this the expected behavior or a bug?

Florimond commented 4 years ago

Hello @postacik ,

is this the behavior of the code on master? I'm afraid the nugget is still not up to date...

postacik commented 4 years ago

Hi @Florimond ,

I just downloaded the master channel code and ran the following test code:

            using (var emitter = Connection.Establish(channelKey, "localhost", 8082, false))
            {
                emitter.Subscribe(channel,
                    (chan, msg) => { Console.WriteLine("Handler1 :" + Encoding.UTF8.GetString(msg)); });
                emitter.Subscribe(channel,
                    (chan, msg) => { Console.WriteLine("Handler2 :" + Encoding.UTF8.GetString(msg)); });
                string text = "";
                Console.WriteLine("Type to chat or type 'q' to exit...");
                do
                {
                    text = Console.ReadLine();
                    emitter.Publish(channelKey, channel, text, Options.WithTTL(3600));
                }
                while (text != "q");
            }

Here's the output:

Type to chat or type 'q' to exit...
postacik
Handler1 :postacik
message
Handler1 :message
test
Handler1 :test

Only "Handler1" is triggered.

However, in my application, the same channel is subscribed in different classes and they update their user interface from the message respectively. So I need all handlers to be triggered.

Florimond commented 4 years ago

If I remember correctly, the handler should be updated so the second handler is called instead of the first one. That might be a bug indeed.

However, there is only one handler per channel. It's designed like that in the other SDKs as well. There is one consumer per channel and unsubscribing is therefore straightforward. Can't you achieve what you want with the sub-channel mechanism?

postacik commented 4 years ago

I have my own JavaScript, C# and Dart libraries.

I had copied the ReverseTrie algorithm from an old version of this project and in my implementations the second handler overrides the first one as you mentioned and only the last inline handler is triggered.

Anyway, if your latest implementation also has a similar behavior and if that's by design, I have to improve my ReverseTrie algorithm.

Thanks :)

postacik commented 4 years ago

Here's my ReverseTrie implementation which supports multiple subscriptions and inline message handlers for a channel:

    public class ReverseTrie<T> where T : class
    {
        private readonly Hashtable Children;
        private readonly int Level = 0;
        private List<T> Value = default(List<T>);

        public ReverseTrie(int level)
        {
            this.Level = level;
            this.Children = new Hashtable();
        }

        public void RegisterHandler(string channel, T value)
        {
            this.Add(CreateKey(channel), 0, value);
        }

        public void UnregisterHandler(string channel)
        {
            this.TryRemove(CreateKey(channel), 0);
        }

        private List<T> RecurMatch(string[] query, int posInQuery, Hashtable children)
        {
            var matches = new List<T>();
            if (posInQuery == query.Length)
                return matches;
            if (Utils.TryGetValueFromHashtable(children, "+", out object objPlusChildNode))
            {
                var childNode = objPlusChildNode as ReverseTrie<T>;
                if (childNode.Value != default(List<T>))
                    matches.AddRange(childNode.Value);
                matches.AddRange(RecurMatch(query, posInQuery + 1, childNode.Children));
            }
            if (Utils.TryGetValueFromHashtable(children, query[posInQuery], out object objQueryChildNode))
            {
                var childNode = objQueryChildNode as ReverseTrie<T>;
                if (childNode.Value != default(List<T>))
                    matches.AddRange(childNode.Value);
                matches.AddRange(RecurMatch(query, posInQuery + 1, childNode.Children));
            }
            return matches;
        }
        public List<T> Match(string channel)
        {
            var query = CreateKey(channel);
            var result = RecurMatch(query, 0, this.Children);
            return result;
        }

        public static string[] CreateKey(string channel)
        {
            return channel.Trim('/').Split('/');
        }

        private object Add(string[] key, int position, T value)
        {
            if (position == key.Length)
            {
                lock (this)
                {
                    // There's already a value
                    if (this.Value == default(List<T>))
                        this.Value = new List<T>();
                    this.Value.Add(value);
                    return this.Value;
                }
            }

            // Create a child
            var child = Utils.GetOrAddToHashtable(Children, key[position], new ReverseTrie<T>(position)) as ReverseTrie<T>;
            return child.Add(key, position + 1, value);
        }

        private bool TryRemove(string[] key, int position)
        {
            if (position == key.Length)
            {
                lock (this)
                {
                    if (this.Value == default(List<T>))
                        return false;

                    this.Value = default(List<T>);
                    return true;
                }
            }

            // Remove from the child
            object child;
            if (Utils.TryGetValueFromHashtable(Children, key[position], out child))
                return ((ReverseTrie<T>)child).TryRemove(key, position + 1);

            return false;
        }
    }
Florimond commented 4 years ago

I only glanced over the code... You can add several handlers to a node indeed, but it doesn't seem like you have the choice to remove only one specific handler with this solution. You seem remove all handlers at once. Which solution I'm not fond of...

postacik commented 4 years ago

You are right but if you unsubscribe a channel, all of the handlers can be disposed because you'll not receive any more events from the emitter server. That's what the given code does. Clears all handlers if unsubscribed.