Giter Site home page Giter Site logo

ostinelli / syn Goto Github PK

View Code? Open in Web Editor NEW
622.0 33.0 74.0 1.61 MB

A scalable global Process Registry and Process Group manager for Erlang and Elixir.

License: MIT License

Makefile 0.30% Erlang 99.61% Shell 0.09%
process-registry cluster pubsub conflict-resolution process-group erlang elixir-lang elixir

syn's People

Contributors

ahovgaard avatar cystbear avatar dziaineka avatar ephe-meral avatar essen avatar francescorestelli avatar kianmeng avatar ostinelli avatar pgeraghty avatar seb3s avatar skirino avatar veedo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

syn's Issues

nodedown should trigger callbacks

I find when a node is down, neither on_process_exit or on_group_process_exit is triggered. Is this behaviour by design, or am I doing something wrong?

Lookup faster than select for ETS

Hi, a colleague show me using a benchmark than the use of ets:select/2 is slower than ets:lookup/2:

Quick benchmark:
                Name        Iters         Op/s          Min          P50          Avg          P90          P95          Max
          ets lookup       350000      4525963     201.6 ns     207.8 ns     220.9 ns     224.1 ns     233.9 ns     1.298 us
          ets select        80000       999365     917.3 ns     977.0 ns     1.001 us     996.8 ns     1.032 us     5.244 us

In the implementation of the registry, even when it is not needed, you use it:

case ets:select(syn_registry_by_name, [{

What's the advantage? I mean, I can see here:

find_registry_entry_by_name(Name) ->
    case ets:select(syn_registry_by_name, [{
        {{Name, '$2'}, '$3', '_', '_', '_'},
        [],
        ['$_']
    }]) of
        [RegistryTuple] -> RegistryTuple;
        _ -> undefined
    end.

You are using {Name, Pid} as the key and in the other ETS table syn_registry_by_pid, you are using {Pid, Name}, what is the improvement instead of using as two elements in the beginning of the tuple?

Syn never reconciles 2 processes with the same name

Hello Roberto,

I'm facing an issue with syn where duplicate processes with the same name can be registered (on different Erlang nodes) and syn never kills the duplicates.

I've tried with both :syn v1.6.3 and with v2.0-rc.1 and it reproes with both.

My repro steps are:

  1. Start 3 Erlang nodes on the same machine
  2. Interconnect them into Erlang cluster
  3. Call :syn.init on each one (if using v1.6.3)
  4. Simultaneously start a process on all 3 nodes that, in a loop, sends :ping to {:via, :syn, "123"} using GenServer.call and creates it locally (and registers with syn) if it's not found, sleeps for 1sec and repeats. The "123" process simply replies :pong to :ping calls.
  5. Often, at this point, I would have more than one process named "123" running on different nodes.
  6. If the issue didn't repro in step 5, kill the OS process hosting the Erlang node where "123" is running. Both remaining nodes will create and register their own instance of "123" process.

And while it's unfortunate that multiple processes with the same name "123" can be created (it would be more desirable for us if registration for duplicates were to fail), the worst issue is that this split-brain situation never gets resolved. According to documentation, we expect one of the duplicate "123" processes to be killed by syn, but it never happens.

I would appreciate any ideas/workarounds/fixes.
Please let me know if you need more information or have any questions.

Thank you, Dmitry.

Using syn in dynamic nodes loading topology

Given an app, which its nodes are not determined at the start, can Syn be used in that context?

The app in my case can accept new manually started nodes to scale performance. My problem is: The connection between the new node and the cluster can be initiate from the new node, or from one of the cluster old nodes.

From what I see in the syn source, at syn:init, there is a step to initialize mnesia table on the current nodes list of the current node. After, it tries to duplicate the syn table to the current node if it is already exists on other nodes.

Is there a functionality in Syn that I can use from the connection initializer node to initialize Syn on other nodes after connecting to them ?

Add a timeout option to multi_call

Would be good to have the ability to configure the timeout value for syn:multi_call. For example multi_call/3 with an added Timeout argument. 5000 is a good default but it doesn't fit all use cases.

Additionally it might be worth to monitor to exit early if a crash happened. I think this functionality should try to mirror the gen:call mechanism as much as possible, otherwise it can be dangerous.

Timeouts from remote nodes don't have any consequence

I'm using Syn in an environment where hosts are being dynamically deployed, i.e. they join and leave the cluster without manual configuration and without control from within the node. Sometimes this leads to inconsistencies, where process that were registered on a remote node will be still in the local table, but will not respond because the remote node doesn't respond.
This leads to timeouts, even when trying to unregister the name:

** (stop) exited in: :gen_server.call({:syn_registry, :"[email protected]"}, {:unregister_on_node, {:my_proc, "some-name"}})

Now I would assume that these timeouts eventually lead to a check of the node and it being removed from the known cluster, but apparently it just stays there and the entries will remain broken. Which leads (in my case) to an endless loop of start -> try to unregister the name (to be able to register oneself) -> timeout -> crash -> restart -> ...

So the question is, is there already some function I can call from the point of the node that detects the timeouts, or can we maybe improve syn so it can handle these situations more automatically?

Edit: Some more info on this: The node in question seems to be alive (i.e. running in a way that it's not kicked from the erlang cluster) but at least the app running our businesslogic crashed internally or hangs or what, which results in the timeouts.

Names are not shared between nodes after a conflict resolution

Testing with two nodes, after a conflict resolution, syn:whereis_name will sometimes return undefined on one of the two nodes. It can be the node where the process was shutdown (general case), it can be the other node, or it will also work as intended and the call on both node will return the same pid (in its local form and its remote form depending on the node the function is called).

Please see this commit where I tried to implement a test: lud@8c29218 .

Non-backward-compatible change in `:syn_registry.register_or_update/4`

Hello!
We were transitioning from syn on version 3.2.1, to version 3.3.0 but when rolling out the upgrade, we have noticed the following error originating in the nodes with the latest syn:

GenServer X terminating
** (CaseClauseError) no case clause matching: {:ok, {:on_process_registered, [_meta], _ts, :syn_registry_by_name, :syn_registry_by_pid}}
    (syn 3.3.0) /app/deps/syn/src/syn_registry.erl:119: :syn_registry.register_or_update/4
    (syn 3.3.0) /app/deps/syn/src/syn_registry.erl:87: :syn_registry.register/4
...

Similar error was reported by the nodes in the cluster that were running the previous version of syn:

GenServer Y terminating
** (CaseClauseError) no case clause matching: {:ok, {:on_process_registered, :undefined, [_meta], _ts, :syn_registry_by_name, :syn_registry_by_pid}}
    (syn 3.2.1) /app/deps/syn/src/syn_registry.erl:119: :syn_registry.register_or_update/4
    (syn 3.2.1) /app/deps/syn/src/syn_registry.erl:87: :syn_registry.register/4

The issue seems to be caused by the commit Add update callbacks with previous meta info, because now we might get a tuple of unexpected arity from the syn_gen_scope:call on both nodes (version 3.3.0 may get result in the shape {ok, {CallbackMethod, Meta, Time, TableByName, TableByPid}} which won't match and version 3.2.1 will fail to match if the second tuple contains the additional PreviousMeta).

Making the change backwards compatible should probably be as easy as adding an extra clause in the case statement that matches if the tuple is missing PreviousMeta and we can possibly get rid of this once releasing syn to a new major version.

However, to make upgrades smooth we would also need the change to be forward-compatible. That could have been achieved by returning a map instead of the tuple, therefore the old version could ignore the added key.
But for that it is probably too late, hehe.

Would it make sense to add the extra clause to handle tuples without PreviousMeta in order to at least mitigate the issue?
I'd be more than happy to help, of course!

Thanks for the library, it has been serving us very well:)

syn and syn_groups' local_member and member function args are not aligned

Hi,

syn:member written as;

member(GroupName, Pid) ->
syn_groups:member(GroupName, Pid).

but syn_groups:member is declared as;

member(Pid, GroupName)

as I checked syn_groups other functions, to make alignment with them, seems syn_groups:member should declare as

member(GroupName, Pid)

like syn_groups:leave function.

There is same issue for local_member on syn and syn_groups modules.

I've fixed these issues on
https://github.com/tsenturk/syn/pull/new/fix-syn-groups-member-local-member-declare

#56

but not sure, eventually it seems a breaking change.

Leaving on process group misfunctioning

When we leave a process that belongs to multiple groups, it is unlinked. This should be the case only when that process belongs to one group and only one. Otherwise the gen_server is no more able to track EXIT messages to properly clean the other groups that process belongs to when it will die.

syn:join fails for some Names

You claim that Name is any(), but there is an issue:

syn:join({"topic",1212},self()).

** {{aborted,{badarg,[syn_pg_table,
                      [{{syn_pg_table,'$1','$2','_'},
                        [{'=:=','$1',{"topic",1212}},{'=:=','$2',<0.878.0>}],
                        ['$2']}]]}},

I fixed this by using term_to_binary:

syn:join(term_to_binary({topic,"1212"}),self()).
ok

I am not sure how to fix this. Would it be OK to just wrapping Name with term_to_binary? Or the possible solution lies deeper in the code?

Performing a lookup in a non-existent scope will result in missing all existing scopes.

Hello, dears.

I am using version 3.3.0. Suppose we add node to a scope like this: syn:add_node_to_scopes([users]) and register a process like this:

Pid = self().
syn:register(users, "hedy", Pid).

If I then call syn:lookup with a non-existent scope name like this:

syn:lookup(sers, "hedy").

I will get this error:

** exception error: {invalid_scope,sers}
     in function  syn_registry:lookup/2 (_build/default/lib/syn/src/syn_registry.erl, line 76)

If I call syn:lookup with an existing scope like this:

syn:lookup(users, "hedy").

I will get undefined. If I try to register Pid again, I will get the following error:

{error,not_alive}

Can you help me understand what is incorrect? Also, why is it that if we perform a lookup in a non-existent scope, all existing scopes will be missed?

Feature request (anycast)

I found myself implementing this feature many times and I think that this feature would be useful to be shipped with syn.
By anycast I mean sending message to one process of the group which is choosen with some algorithm.

Possible algorithms:

  • Random member of group
  • Round robin
  • Random process on the same node

I don't think that in would be useful, but anycall would make sense too

Registering processes timing out

Hi,

I do have to register 2k processes (sometimes more) at the exact same time. And sometimes join/2 was timing out. Is there a solution about that?

Thanks.

UPDATE: I just saw your blog post with the syn benchmark. I don't understand why I cannot achieve that. Registry messages keep getting stuck in the :syn_groups genserver. The only possible reason I could find is that I also heavily do :mnesia inserts, maybe it is slowing down mnesia? (doing something like 15M inserts in a 30 minutes frame)

UPDATE2:
So to avoid the timeout thing, I forked the project and added async_join just to see how fast it would add the processes to the group.
The :syn_groups genserver went up to 120k (I have something like 330k processes to add to the group) join messages in its mailbox and took approx. 30 minutes to handle them all. It was mostly hanging on :mnesia functions. Feels like the :bag type table is just slow.

Add :local / :global semantics

I recognize that being able to share group names / process keys across nodes is the primary purpose; however, there are several cases where I'd like to be able to restrict the sharing to the local node, yet retain the API and functionality of :syn.

Specifically, I'm looking at a homogenous codebase that is started in N instances; for each of them I'd like to be able to create multiple groups of the form {local_events, EventType}. Using the current API for :syn, I'd need to add a Node key to the tuple, e.g. {local_events, EventType, node()} to ensure uniqueness across nodes.

The same general usage applies to Keys: I need an {event_coordinator, EventType} on each node, yet unless it is tagged with the node as well, there will be conflicts.

I am happy using {..., node()} as a work around, but this would be a useful addition to the API in my view.

Enable gen_server name via tuples for process groups

Currently we only support gen_server via tuples for single processes, i.e. you can start a gen_server with a name that is managed by syn, but that name cannot be a process group.

It would be interesting to transparently support groups as well, because you can then use one gen_server:cast/2 to send a cast to a group of processes, managed by syn.

gproc has this feature, by means of matching on different naming schemes:

{n, g, <<"your name">>}. %% would be a global name for a process
{p, g, <<"your name">>}. %% would be a global name for a process group

Both of these can be used with the gen_server name API and its simple for the registry to distinguish between single processes and groups that way.

While this is one possibility, we could also simply add another branch in syn:send/2 that gets executed when the name was not registered for a single process and simply publishes to a group with the given name, hoping that it exists - though thats seemingly a more dirty solution.

send(Name, Message) ->
    case whereis_name(Name) of
        undefined ->
            case syn_groups:publish(Name, Message) of
                {ok, 0} -> {badarg, {Name, Message}};
                {ok, _} -> ok
            end;
        Pid -> Pid ! Message, Pid
    end.

Generally I really like that gproc's interface for registering names and groups etc. is the same for both and works by distinguishing between them with these special tuples.

Error on exit in Elixir

I'm getting error on exit with reason {:shutdown, :left} inside elixir phoenix. Is it possible to customize the handle_info EXIT in syn_registry.erl to avoid this error?

can syn be used only on parts of nodes in cluster

for example, i have node A_1, A_2, B_1, B_2, and i only run syn on node A_1, A_2, will cause error

Terminating with reason: {badarg,[{erlang,length,[{badrpc,{'EXIT',{undef,[{syn_registry,sync_get_local_registry_tuples,['[email protected]'],[]},{rpc,'-handle_call_call/6-fun-0-',5,[{file,"rpc.erl"},{line,197}]}]}}}],[]},{syn_registry,'-handle_info/2-fun-1-',2,[{file,"src/syn_registry.erl"},{line,237}]},{global,trans,4,[{file,"global.erl"},{line,430}]},{syn_registry,handle_info,2,[{file,"src/syn_registry.erl"},{line,230}]},{gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,616}]},{gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,686}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

Not unregistering exited processes

I'm having an issue where it seems like syn is not unregistering processes when they exit. I'm able to get a pid from syn:whereis_name but if I try to send messages to the result I get a no such process error. I've verified the process indeed does not exist.

Any idea?

Callback for on process enter

It would be nice to have a local callback for when a progress has been added to the registry in the same way there is an exit and on conflict. This way I don't have to poll the group/etc for new membership, etc.

registry_process_enter_callback, [my_callback, callback_on_process_enter]

callbacks improvements

Can you add also callbacks when a process join a group or a new pid is registered ? Right now I see callbacks are only when a pid leaves a group...

Silviu

Turn off logging when process disconnects

Hi,

First of all - great work! This library is solving a lot of issues for me, and with great ease! Thanks!
My only issue is that when a process disconnects, syn prints an error log (syn_groups.erl#L264) - I don't see in the code any way to turn that off, or maybe I'm missing something? For my project, it's very normal for a process to terminate in an "unhealthy" way, which means that my log is full of supposed "errors".
Would it be possible to allow turning this logger off? Thanks!

Add time to CustomEventHandler:resolve_registry_conflict

Custom resolve_registry_conflict function can also rely on Time.
Can we extend (Name, {Pid1, Meta1}, {Pid2, Meta2}) tuples with Time like in do_resolve_registry_conflict :
Name, {Pid1, Meta1, Time1}, {Pid2, Meta2, Time2} ?

Process entry do not cleaned up if register under more then one key.

The i_find_by_pid(...) at.

https://github.com/ostinelli/syn/blob/master/src/syn_backbone.erl#L331

expects a list with only one element which is not correct due to process can be registered more than once, i.e. under several different aliases. As a consequence, such a process will never be cleaned up and table will "leak".

I have tried to solve this issue in an ongoing pull request(just as an illustration), but it is required to change the return type of find_by_pid/1 function to list

Add meta to processes in groups

It would be nice if I could have a group with a set of keys inside of it. For example: I have a collection of processes that belong to a group. However I need to make sure that certain requests always goes to a specific process within the group.

Group -> [{Key, Pid}, {Key2, Pid2}]

This way I can hash the requester ID to one of these Pid's in the group. Right now I have to pull all the processes out of the group and then pull key for each process, then do my mapping.

Example Elixir code: If Erlang is needed I can rewrite it

def processForKey(key) do
   # Basic list comprehension to get all the pids with their keys (keys are integers)
   members =   for pid <- :syn.get_members(:mygroup) do
                            pidKey = :syn.find_by_pid(pid)
                            {pidKey, pid}
                        end

    # sort the keys
    members = Enum.sort(fn({k1, _}, {k2, _}) -> k1 > k2 end)

    # pull out the process that should map to the passed in key
    {_, pid} = Enum.at(members, rem(:erlang.phash2(key), length(members)))
    pid
end

It would be nice if when adding the group I could add a meta/key value that I can pull out with a call like

:syn.get_members(:my group, :with_meta)

Thanks!

Add a remove_node helper

In case a node is permanently removed from a cluster, allow to remove it from the underlying mnesia database db_nodes.

Add Support for Elixir

We need to add support for Elxiir "via-tuple" ProcessRegistry API.
Note: Gproc already supports this, making usage easier in Elixir without introducing another wrapper.

Example Target Usage:

GenServer.start_link(My.GenServer.Module, [], [name: {:via, :syn, "my-key"}])
GenServer.cast({:via, :syn, "my-key"}, :message)
GenServer.call({:via, :syn, "my-key"}, :message)

Multi aliases registration for process.

I think the multi aliases feature should be live, because only library users can know real purpose of registration.
I need register process by several aliases. It is very useful for me.
I can make patch for that, but not sure that it will be included in main code.

i met:

1> syn:register(a, self()).
ok
2> syn:register(b, self()).
{error,pid_already_registered}

how i can use syn for multi aliases registration ?
thanks.

Event handler not called

Hi, I was trying to see if I could properly shutdown a process on name conflict (I need it to finish work before being killed).

But I cannot have my event handler to be called. (By the way the docs tells that syn:set_event_handler/1 returns ok but that is not the case.

Here is my test code, that I run with two shells as iex --sname alice tt.exs and the "bob" version, and then call H.connect/0.

Mix.install([{:syn, "~> 3.0"}])

defmodule X do
  use GenServer

  def init(_), do: {:ok, []}

  def handle_info(info, state) do
    info |> IO.inspect(label: ~S[info])
    {:noreply, state}
  end

  def terminate(reason, state) do
    reason |> IO.inspect(label: ~S[reason])
    {:noreply, state}
  end
end

defmodule Ev do
  @behaviour :syn_event_handler
  def resolve_registry_conflict(name, pid_a, pid_b) do
    binding |> IO.inspect(label: ~S[resolve_registry_conflict])
    pid_a |> IO.inspect(label: ~S[keep])
    raise "not called"
  end

  def on_process_joined(scope, group_name, pid, meta, reason) do
    binding |> IO.inspect(label: ~S[on_process_joined])
  end
end

defmodule H do
  def connect do
    case node() do
      :"alice@lud-elitebook" -> :"bob@lud-elitebook"
      :"bob@lud-elitebook" -> :"alice@lud-elitebook"
    end
    |> Node.connect()
  end
end

[_ | _] = :syn.set_event_handler(Ev)
:syn.add_node_to_scopes([:spool_scope])
GenServer.start_link(X, nil, name: {:via, :syn, {:spool_scope, :myspool}})

Can you see if I am doing something wrong?

Thank you.

Mixed-Cluster Versioning

We have a cluster of nodes running our product that is compiled with syn 2.0.1.
I'm trying to update nodes one by one (using rolling update) to a newer version of our product compiled with syn 2.1.1.
But new nodes fail to start because of the following syn failure:

2020-06-17 00:36:26.923 [info ] Syn(:"[email protected]"): Terminating with reason: {:function_clause, [{:syn_registry, :"-registry_automerge/2-fun-0-", [{"GlobalSelfScaler", #PID<37561.2151.0>, :undefined}], [file: '/opt/app/deps/prod/syn/src/syn_registry.erl', line: 648]}, {:lists, :foreach, 2, [file: 'lists.erl', line: 1338]}, {:syn_registry, :"-registry_automerge/2-fun-1-", 2, [file: '/opt/app/deps/prod/syn/src/syn_registry.erl', line: 652]}, {:global, :trans, 4, [file: 'global.erl', line: 425]}, {:syn_registry, :handle_info, 2, [file: '/opt/app/deps/prod/syn/src/syn_registry.erl', line: 406]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 637]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 711]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}
	| (pid=<0.1528.0> )
2020-06-17 00:36:26.923 [error] GenServer :syn_registry terminating
** (FunctionClauseError) no function clause matching in anonymous fn/1 in :syn_registry.registry_automerge/2
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:648: anonymous fn({"GlobalSelfScaler", #PID<37561.2151.0>, :undefined}) in :syn_registry.registry_automerge/2
    (stdlib 3.12.1) lists.erl:1338: :lists.foreach/2
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:652: anonymous fn/2 in :syn_registry.registry_automerge/2
    (kernel 6.5.2) global.erl:425: :global.trans/4
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:406: :syn_registry.handle_info/2
    (stdlib 3.12.1) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib 3.12.1) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:nodeup, :"[email protected]"}
State: {:state, :syn_event_handler, :undefined, :undefined, #PID<0.1530.0>}	| (pid=<0.1528.0> module=gen_server function=error_info/7 line=889 )
2020-06-17 00:36:26.924 [error] Process :syn_registry (#PID<0.1528.0>) terminating
** (FunctionClauseError) no function clause matching in anonymous fn/1 in :syn_registry.registry_automerge/2
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:648: anonymous fn({"GlobalSelfScaler", #PID<37561.2151.0>, :undefined}) in :syn_registry.registry_automerge/2
    (stdlib 3.12.1) lists.erl:1338: :lists.foreach/2
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:652: anonymous fn/2 in :syn_registry.registry_automerge/2
    (kernel 6.5.2) global.erl:425: :global.trans/4
    (syn 2.1.1) /opt/app/deps/prod/syn/src/syn_registry.erl:406: :syn_registry.handle_info/2
    (stdlib 3.12.1) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib 3.12.1) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Initial Call: :syn_registry.init/1

Feature request (group count)

Just like process registries have a syn:registry_count() and syn:registry_count(Node), would it be possible to have something similar for groups (get the total number of groups and number of local groups)?
I'm wondering if there's a way to do that without impacting performance (maybe adding counters somewhere).
If you point me in the right direction I could also look at how to implement this myself. Thank you!

Elixir crash on startup

Following the instructions in the readme, I have the following application section in Mix.exs:

  def application do
    [applications: [:logger, :syn],
     mod: {MyMod, []}]
  end

And in MyMod.start/2:

  def start(_type, _args) do
    import Supervisor.Spec, warn: false
    connect_nodes() # currently does nothing
    :syn.init
    children = [] # for later

    opts = [strategy: :one_for_one, name: MyMod.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

When I start the application via iex -S mix, I get the following error:

** (Mix) Could not start application my_mod: exited in: MyMod.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (MatchError) no match of right hand side value: {:error, {:aborted, {:node_not_running, :nonode@nohost}}}
            src/syn.erl:63: :syn.init/0
            (my_mod) lib/my_mod.ex:7: MyMod.start/2
            (kernel) application_master.erl:273: :application_master.start_it_old/4

Adding :mnesia.start before :syn.init allows the application to start, but I'm concerned that there might be a side-effect I'm not anticipating, or an issue I might not see in single-node mode.

connection between nodes

hi,
Trying to get started with syn, but it doesn't seem to work :
I have two node in my machine (foo@my-pc and bar@my-pc).
in foo i have a simple genserver that is started with this name : {:via, :syn, :example_server},
and handles a :get call.
running : GenServer.whereis({:via, :syn, :example_server})
gives me the pid.
My problem is in bar:
I connected the nodes :
Node.connect :'foo@my-pc'
And than tried running GenServer.whereis({:via, :syn, :example_server})
but i get nil
and of course GenServer.call({:via, :syn, :example_server}, :get) doesn't work :(
What am i missing ?

Syn rejects some names

Syn doesn't accept some names as the following snippet shows, despite saying it accepts any().

Probably related to #3 and actually sounds like the problem was fixed in the past and is back.

2> application:ensure_all_started(syn).
{ok,[mnesia,syn]}
3> syn:join({stream, {server, <<"hello">>}}, self()).
** exception exit: {{aborted,
                        {no_exists,
                            [syn_groups_table,
                             [{{syn_groups_table,'$1','$2','_','_'},
                               [{'==','$1',{{stream,{server,<<"hello">>}}}},
                                {'=:=','$2',<0.96.0>}],
                               ['$_']}]]}},
                    {gen_server,call,
                        [{syn_groups,nonode@nohost},
                         {join,{stream,{server,<<"hello">>}},<0.96.0>,undefined}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)
4> 
=INFO REPORT==== 6-Apr-2017::10:22:33 ===
Terminating syn_groups with reason: {aborted,
                                     {no_exists,
                                      [syn_groups_table,
                                       [{{syn_groups_table,'$1','$2','_','_'},
                                         [{'==','$1',
                                           {{stream,{server,<<"hello">>}}}},
                                          {'=:=','$2',<0.96.0>}],
                                         ['$_']}]]}}
=ERROR REPORT==== 6-Apr-2017::10:22:33 ===
** Generic server syn_groups terminating 
** Last message in was {join,{stream,{server,<<"hello">>}},<0.96.0>,undefined}
** When Server state == {state,undefined,undefined}
** Reason for termination == 
** {{aborted,{no_exists,[syn_groups_table,
                         [{{syn_groups_table,'$1','$2','_','_'},
                           [{'==','$1',{{stream,{server,<<"hello">>}}}},
                            {'=:=','$2',<0.96.0>}],
                           ['$_']}]]}},
    [{mnesia,abort,1,[{file,"mnesia.erl"},{line,318}]},
     {syn_groups,i_find_by_pid_and_name,2,
                 [{file,"/home/essen/tmp/syn/_build/default/lib/syn/src/syn_groups.erl"},
                  {line,319}]},
     {syn_groups,handle_call,3,
                 [{file,"/home/essen/tmp/syn/_build/default/lib/syn/src/syn_groups.erl"},
                  {line,180}]},
     {gen_server,try_handle_call,4,[{file,"gen_server.erl"},{line,615}]},
     {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,647}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

syn doesn't seem to deregister a process in some cases

It looks like in some cases when starting a gen_server process via syn through a supervisor if the process crashes and the supervisor restarts it I get an already_started error which I imagine is because the process isn't unregistered fast enough (?) I am just guessing here.

Anyway if I replace the {via, syn, Name} with {local, Name} it works as expected (but naturally I want to use syn to be able to name my processes with terms rather than atoms).

I attached a zip that contains the stuff that I just started working on. If you fire up the docker containers and then start the app with rebar3 shell, in the shell you can call kyu:kill(test). and that should do the trick (the problem doesn't always occur at first but if you keep trying it will happen).

kyu.zip

Looks like nodes does not sync between each other

Hi,

I am currently trying to setup Syn with 2-nodes cluster app. Here are steps to reproduce my case:

  • At first at my app start/2 I connect nodes between each other via net_kernel:connect/1;
  • Next I call syn:init/0 to initialize Syn itself;
  • After initialization during app is working, I call syn:join({user, UserId}, self(), [ %% META_HERE %%]).;
  • Later to check that everything synced, I call syn:get_members({user, UserId}, with_meta)., and get
    [{ <0.1234.0>, [ %% META_HERE %% ] }] at first server and [] at second one.

For sure node/0 and nodes/0 shows that my first and second nodes inside same cluster.

Furthermore if I manually shutdown one of node, other node echo this log message
Received a MNESIA down event, removing on node...
in my opinion this means that nodes been connected and Syn correctly handled shutting down one of them.

Please hint me what I do wrong, and possible way to bypass and/or debug this issue.
Also I can provide any additional information.

The consistency of group membership

Hi @ostinelli, I found this case which would be nice to classify first (before more detailed test and clarification).

syn:is_member/3
syn:members/2

The first call makes the negative false until syn:members/2 invocation has done from the client side.

It seems that the second call ensures the consistency while the first one doesn't.

If so (is_member/3 isn't consistent by nature) the right doc could be done (to describe the feature properly).

If not (there is a bug 🐛) maybe I could help (at least to give more info and try to reproduce via ct test).

What do you think?

Best regards, Slava

Which OTP versions are supported?

I didn't see a description anywhere which versions are supported. I would assume R18 since that was around the time this was written, but I am also curious about R19.

Support registering metadata in via-tuples

I would like to suggest extending the via-tuple support to allow registering metadata as well. This would enable one to do as follows:

iex> tuple = {:via, :syn, {:devices, "SN-123-456789", [meta: :one]}}
{:via, :syn, {:devices, "SN-123-456789", [meta: :one]}}
iex> GenServer.start_link(__MODULE__, [], name: tuple)
{ok, #PID<0.105.0>}
iex> :syn.lookup(:devices, "SN-123-456789")
{#PID<0.105.0>, [meta: :one]}

For reference, registering a value in a via-tuple is already supported by the Elixir Registry, see: https://hexdocs.pm/elixir/1.12/Registry.html#module-using-in-via

If this feature is considered appropriate and useful, I would like to implement it and make a PR with the changes.

Pid registered on more than one Group is not removed from them when is killed

on syn_groups just the case of a process being in one group is handled when a process receive Exit signal

handle_info({'EXIT', Pid, Reason}, State) ->
    %% check if pid is in table
    case i_find_by_pid(Pid) of
        undefined ->
            %% log
            case Reason of
                normal -> ok;
                killed -> ok;
                _ ->
                    error_logger:error_msg("Received an exit message from an unlinked process ~p with reason: ~p", [Pid, Reason])
            end;

        Process ->
            %% get group
            Name = Process#syn_groups_table.name,
            %% log
            case Reason of
                normal -> ok;
                killed -> ok;
                _ ->
                    error_logger:error_msg("Process of group ~p and pid ~p exited with reason: ~p", [Name, Pid, Reason])
            end,
            %% delete from table
            remove_process(Process)
    end,

#######

i_find_by_pid(Pid) ->
    case mnesia:dirty_index_read(syn_groups_table, Pid, #syn_groups_table.pid) of
        [Process] -> Process;
        _ -> undefined
    end.

syn_event_handler per process/group

This discussion shouldn’t be considered as an issue, rather as a feature.

The main point that is Syn can be used by different applications under the common shared release.

{syn, [
    {event_handler, my_custom_event_handler}
]}

This global setup listed above doesn’t meet the criteria:

  1. Each app can share it's own Syn space and manage process creation/exits by independent way;
  2. The root app shouldn’t rewrite the event_handler config by it's own value. The current design cause one way which is top to bottom registry

The solution:

register(Name, Pid, Meta, Handler);
join(GroupName, Pid, Meta, Handler);

Where each interested party can setup their own event_handler within particular called scope. This argument will have the higher priority then env parameter and would provide the users with flexibility to manage their own registry scopes.

P.S. If this feature is considered as useful I may provide appropriate PR as solution, thanks in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.