a Framework for creating mesh networks using technologies and design patterns of Erlang/OTP in Golang

Ergo Framework

GitHub release Go Report Card GoDoc MIT license codecov Build Status

Implementation of Erlang/OTP in Golang. Up to x5 times faster than original Erlang/OTP. The easiest drop-in replacement for your hot nodes in the cluster.

Purpose

The goal of this project is to leverage Erlang/OTP experience with Golang performance. Ergo Framework implements OTP design patterns such as GenServer/Supervisor/Application and makes you able to create high performance and reliable application having native integration with Erlang infrastructure

Features

  • Erlang node (run single/multinode)
  • embedded EPMD (in order to get rid of erlang' dependencies)
  • Spawn Erlang-like processes
  • Register/unregister processes with simple atom
  • GenServer behavior support (with atomic state)
  • Supervisor behavior support (with all known restart strategies support)
  • Application behavior support
  • Connect to (accept connection from) any Erlang node within a cluster (or clusters, if running as multinode)
  • Making sync/async request in fashion of gen_server:call or gen_server:cast
  • Monitor processes/nodes
    • local -> local
    • local -> remote
    • remote -> local
  • Link processes
    • local <-> local
    • local <-> remote
    • remote <-> local
  • RPC callbacks support
  • Experimental observer support
  • Unmarshalling terms into the struct using etf.TermIntoStruct
  • Support Erlang 22. (with fragmentation)
  • Encryption (TLS 1.3) support
  • Tested and confirmed support Windows, Darwin (MacOS), Linux

Requirements

  • Go 1.15.x and above

Benchmarks

Here is simple EndToEnd test demonstrates performance of messaging subsystem

Hardware: laptop with Intel(R) Core(TM) i5-8265U (4 cores. 8 with HT)

Sequential GenServer.Call using two processes running on single and two nodes

❯❯❯❯ go test -bench=NodeSequential -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/halturin/ergo
BenchmarkNodeSequential/number-8 	  256108	     48578 ns/op
BenchmarkNodeSequential/string-8 	  266906	     51531 ns/op
BenchmarkNodeSequential/tuple_(PID)-8         	  233700	     58192 ns/op
BenchmarkNodeSequential/binary_1MB-8          	    5617	   2092495 ns/op
BenchmarkNodeSequentialSingleNode/number-8         	 2527580	      4857 ns/op
BenchmarkNodeSequentialSingleNode/string-8         	 2519410	      4760 ns/op
BenchmarkNodeSequentialSingleNode/tuple_(PID)-8    	 2524701	      4757 ns/op
BenchmarkNodeSequentialSingleNode/binary_1MB-8     	 2521370	      4758 ns/op
PASS
ok  	github.com/halturin/ergo	120.720s

it means Ergo Framework provides around 25000 sync rps via localhost for simple data and around 4Gbit/sec for 1MB messages

Parallel GenServer.Call using 120 pairs of processes running on a single and two nodes

❯❯❯❯ go test -bench=NodeParallel -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/halturin/ergo
BenchmarkNodeParallel-8        	         2652494	      5246 ns/op
BenchmarkNodeParallelSingleNode-8   	 6100352	      2226 ns/op
PASS
ok  	github.com/halturin/ergo	34.145s

these numbers shows around 260000 sync rps via localhost using simple data for messaging

vs original Erlang/OTP

benchmarks

sources of these benchmarks are here

EPMD

Ergo Framework has embedded EPMD implementation in order to run your node without external epmd process needs. By default it works as a client with erlang' epmd daemon or others ergo's nodes either.

The one thing that makes embedded EPMD different is the behavior of handling connection hangs - if ergo' node is running as an EPMD client and lost connection it tries either to run its own embedded EPMD service or to restore the lost connection.

As an extra option, we provide EPMD service as a standalone application. There is a simple drop-in replacement of the original Erlang' epmd daemon.

go get -u github.com/halturin/ergo/cmd/epmd

Multinode

This feature allows create two or more nodes within a single running instance. The only needs is specify the different set of options for creating nodes (such as: node name, empd port number, secret cookie). You may also want to use this feature to create 'proxy'-node between some clusters. See Examples for more details

Observer

It allows you to see the most metrics/information using standard tool of Erlang distribution. The example below shows this feature in action using one of the examples:

observer demo

Changelog

Here are the changes of latest release. For more details see the ChangeLog

1.1.0 - 2020-04-23

  • Fragmentation support (which was introduced in Erlang/OTP 22)
  • Completely rewritten network subsystem (DIST/ETF).
  • Improved performance in terms of network messaging (outperforms original Erlang/OTP up to x5 times. See Benchmarks)

Examples

Code below is a simple implementation of GenServer pattern examples/simple/GenServer.go

package main

import (
    "fmt"
    "time"

    "github.com/halturin/ergo"
    "github.com/halturin/ergo/etf"
)

type ExampleGenServer struct {
    ergo.GenServer
    process ergo.Process
}

type State struct {
    value int
}

func (egs *ExampleGenServer) Init(p ergo.Process, args ...interface{}) (state interface{}) {
    fmt.Printf("Init: args %v \n", args)
    egs.process = p
    InitialState := &State{
        value: args[0].(int), // 100
    }
    return InitialState
}

func (egs *ExampleGenServer) HandleCast(message etf.Term, state interface{}) (string, interface{}) {
    fmt.Printf("HandleCast: %#v (state value %d) \n", message, state.(*State).value)
    time.Sleep(1 * time.Second)
    state.(*State).value++

    if state.(*State).value > 103 {
        egs.process.Send(egs.process.Self(), "hello")
    } else {
        egs.process.Cast(egs.process.Self(), "hi")
    }

    return "noreply", state
}

func (egs *ExampleGenServer) HandleCall(from etf.Tuple, message etf.Term, state interface{}) (string, etf.Term, interface{}) {
    fmt.Printf("HandleCall: %#v, From: %#v\n", message, from)
    return "reply", message, state
}

func (egs *ExampleGenServer) HandleInfo(message etf.Term, state interface{}) (string, interface{}) {
    fmt.Printf("HandleInfo: %#v (state value %d) \n", message, state.(*State).value)
    time.Sleep(1 * time.Second)
    state.(*State).value++
    if state.(*State).value > 106 {
        return "stop", "normal"
    } else {
        egs.process.Send(egs.process.Self(), "hello")
    }
    return "noreply", state
}
func (egs *ExampleGenServer) Terminate(reason string, state interface{}) {
    fmt.Printf("Terminate: %#v \n", reason)
}

func main() {
    node := ergo.CreateNode("node@localhost", "cookies", ergo.NodeOptions{})
    gs1 := &ExampleGenServer{}
    process, _ := node.Spawn("gs1", ergo.ProcessOptions{}, gs1, 100)

    process.Cast(process.Self(), "hey")

    select {
    case <-process.Context.Done():
        fmt.Println("exited")
    }
}

here is output of this code

$ go run ./examples/simple/GenServer.go
Init: args [100]
HandleCast: "hey" (state value 100)
HandleCast: "hi" (state value 101)
HandleCast: "hi" (state value 102)
HandleCast: "hi" (state value 103)
HandleInfo: "hello" (state value 104)
HandleInfo: "hello" (state value 105)
HandleInfo: "hello" (state value 106)
Terminate: "normal"
exited

See examples/ for more details

Elixir Phoenix Users

Users of the Elixir Phoenix framework might encounter timeouts when trying to connect a Phoenix node to an ergo node. The reason is that, in addition to global_name_server and net_kernel, Phoenix attempts to broadcast messages to the pg2 PubSub handler

To work with Phoenix nodes, you must create and register a dedicated pg2 GenServer, and spawn it inside your node. Take inspiration from the global_name_server.go for the rest of the GenServer methods, but the Spawn must have "pg2" as a process name:

type Pg2GenServer struct {
    ergo.GenServer
}

func main() {
    // ...
    pg2 := &Pg2GenServer{}
    node1 := ergo.CreateNode("node1@localhost", "cookies", ergo.NodeOptions{})
    process, _ := node1.Spawn("pg2", ergo.ProcessOptions{}, pg2, nil)
    // ...
}

Development and debugging

There is a couple of options are already defined that you might want to use

  • -trace.node
  • -trace.dist

To enable Golang profiler just add --tags debug in your go run or go build like this:

go run --tags debug ./examples/genserver/demoGenServer.go

Now golang' profiler is available at http://localhost:9009/debug/pprof

Companies are using Ergo Framework

Kaspersky RingCentral LilithGames

is your company using Ergo? add your company logo/name here

Commercial support

if you are looking for commercial support feel free to contact me via email (halturin at gmail dot com)

Owner
Taras Halturin
Developer | Blender of #golang and #erlang
Taras Halturin
Comments
  • Disable Heuristic String Detection in ergo

    Disable Heuristic String Detection in ergo

    List of non negative integers sent from Elixir is decoded as string by ergo.

    There should be a way to configure or disable this "Heuristic String Detection"?

  • Compilation to arm fails

    Compilation to arm fails

    Describe the bug Compiling to ARM does not work

    To Reproduce

    $ GOARCH=arm go build
    

    Expected behavior Compilation works fine

    Actual behaviour This error is displayed:

    # github.com/ergo-services/ergo/lib/osdep
    ../../go/pkg/mod/github.com/ergo-services/[email protected]/lib/osdep/linux.go:15:11: invalid operation: usage.Utime.Sec * 1000000000 + usage.Utime.Nano() (mismatched types int32 and int64)
    ../../go/pkg/mod/github.com/ergo-services/[email protected]/lib/osdep/linux.go:16:11: invalid operation: usage.Stime.Sec * 1000000000 + usage.Stime.Nano() (mismatched types int32 and int64)
    # github.com/ergo-services/ergo/lib
    ../../go/pkg/mod/github.com/ergo-services/[email protected]/lib/tools.go:166:11: cannot use 4294967000 (untyped int constant) as int value in assignment (overflows)
    

    Environment (please complete the following information):

    • Arch: arm
    • OS: Linux
    • Framework Version [v1.999.210]
    • Number of CPU or (GOMAXPROCS not set)

    Additional context Removing GOARCH fixes it however I need to run few services on ARM

  • RPC call doesn't use timeout when an erlang node is down [v1.2.2]

    RPC call doesn't use timeout when an erlang node is down [v1.2.2]

    Hello!

    I've found a new defect after 1.2.2.

    You can easily reproduce it. Just call CallRPCWithTimeout function when an erlang node is down.

    Log:

    2021/05/11 09:52:16 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:system_info
    2021/05/11 09:52:16 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/05/11 09:52:16 [test@MBP-Alexandr] can't connect to demo@MBP-Alexandr: Can't resolve port for demo@MBP-Alexandr: desired node not found
    

    And the app is stuck

  • The message from the remote peer silently drops if the mailbox is full.

    The message from the remote peer silently drops if the mailbox is full.

    There was an issue #95 I've moved to the discussion https://github.com/ergo-services/ergo/discussions/95 by mistake (misunderstood the real problem).

    There should be a way to handle the situation of overflowing the mailbox during the message delivery from the remote peer. Current implementation just silently drops the message (master branch: https://github.com/ergo-services/ergo/blob/master/node/network.go#L346 and in the current development branch https://github.com/ergo-services/ergo/blob/v210/proto/dist/proto.go#L760)

    Thanks to @jiait for pointing to this issue.

  • RPC call 'active_tasks' returns a wrong result

    RPC call 'active_tasks' returns a wrong result

    Hello.

    I'm using RPC calls from go to erlang node (I'm writing an erlang prometheus exporter) and I do the following RPC call: On erlang node:

    (demo@MBP-Alexandr)12> erlang:statistics(active_tasks).
    [0,0,0,1,0,0,0,0,0,0,0,0,0]
    

    As we can see it is the list.

    So now I expect the same result when I do RPC call from golang to this erlang node:

    activeTasks, err = process.CallRPCWithTimeout(8, node, "erlang", "statistics", etf.Atom("active_tasks"))
    

    But I receive the following result: image

    Could you say there is a workaround for this? I tested and found that almost all lists are returned in the same way.

  • Can Goroutine not be created to handle ServerProcess.handleXXXMessage(...) ?

    Can Goroutine not be created to handle ServerProcess.handleXXXMessage(...) ?

    go test all passed

    Benefits:

    1. ServerProcess.handleXXXMessage(...)will not have to be executed in other goroutines, which improves performance;
    2. gen.ServerProcess.Call/.CallWithTimeout will not be restricted to use inServerProcess.handleXXXMessage(...), reducing user errors, thus improving Ergo's ease of use, robustness and genserver's concurrent processing capability.
  • migrate ServerProcess.CastAfter/Cast/CastRPC to process  for true gen_cast/2

    migrate ServerProcess.CastAfter/Cast/CastRPC to process for true gen_cast/2

    gen.ServerProcessinherits gen.Process, so it is backwards compatible.

    This makes it possible to send Cast messages to other ServerProcesses in Ergo in any Goroutine without restriction, just like gen_server:cast/2 in Erlang. Improved ease of use of the framework.

  • RPC call sometimes returns the error 'timeout' instantly

    RPC call sometimes returns the error 'timeout' instantly

    Hello again :)

    I've found an unexpected behavior when I run RPC Calls from golang to an erlang node.

    Please help me to understand why it happens. Thanks.

    You can find the log from go below. As you may see sometimes for some RPC call command the timeout error is returned instantly after calling RPC for no reason (I have 8 seconds timeout)

    INFO[0018] Erlang collector started fetching metrics    
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    ERRO[0018] Error: timeout, Result: <nil>                
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe1, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe1, 0x59e9, 0x0}}, etf.Tuple{247329, 4241}}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe2, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe2, 0x59e9, 0x0}}, etf.Tuple{247487, 158}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe3, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe3, 0x59e9, 0x0}}, etf.Tuple{247645, 158}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe4, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe4, 0x59e9, 0x0}}, etf.Tuple{247803, 158}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe5, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe5, 0x59e9, 0x0}}, etf.Tuple{247961, 158}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe6, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe6, 0x59e9, 0x0}}, etf.Tuple{2365, 0}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    ERRO[0018] Error: timeout, Result: <nil>                
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe7, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe7, 0x59e9, 0x0}}, etf.Tuple{2368, 0}}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe8, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe8, 0x59e9, 0x0}}, etf.Tuple{2371, 0}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe9, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fe9, 0x59e9, 0x0}}, etf.Tuple{2374, 0}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fea, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fea, 0x59e9, 0x0}}, etf.Tuple{2377, 0}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5feb, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5feb, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fec, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fec, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fed, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fed, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fee, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fee, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fef, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5fef, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff0, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff0, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff1, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff1, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff2, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff2, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff3, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff3, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff4, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff4, 0x59e9, 0x0}}, 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff5, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff5, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff6, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff6, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff7, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff7, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff8, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff8, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff9, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ff9, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffa, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffa, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffb, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffb, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffc, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffc, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffd, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffd, 0x59e9, 0x0}}, 0}
    2021/04/29 19:59:35 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:statistics
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
    2021/04/29 19:59:35 Node control: etf.Tuple{2, "", etf.Pid{Node:"test@MBP-Alexandr", ID:0x3ef, Serial:0x1, Creation:0x1}}
    2021/04/29 19:59:35 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1007 1 1}
    2021/04/29 19:59:35 [test@MBP-Alexandr]. {test@MBP-Alexandr 1007 1 1} got message from etf.Pid{Node:"", ID:0x0, Serial:0x0, Creation:0x0}
    2021/04/29 19:59:35 got reply: etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffe, 0x59e9, 0x0}}
    etf.Tuple{etf.Ref{Node:"test@MBP-Alexandr", Creation:0x1, ID:[]uint32{0x5ffe, 0x59e9, 0x0}}, 0}
    INFO[0018] Erlang collector fetched metrics   
    

    I've created a func to retry failed calls but it's just a workaround:

    func (rpc RPC) runCommand(node string, module string, f string, args ...etf.Term) etf.Term {
    	var res etf.Term
    	var err error
    	for i := 0; i < 5; i++ {
    		if args == nil {
    			res, err = rpc.process.CallRPCWithTimeout(8, node, module, f)
    		} else {
    			if len(args) == 1 {
    				res, err = rpc.process.CallRPCWithTimeout(8, node, module, f, args[0])
    			} else {
    				res, err = rpc.process.CallRPCWithTimeout(8, node, module, f, args)
    			}
    		}
    		if err != nil {
    			log.Errorf("Error: %v, Result: %v\n", err, res)
    			continue
    		}
    	}
    	return res
    }
    
  • Locking...

    Locking...

    I was wondering if node.Send() was safe to call from a worker go routine, so I was digging around in the code and noticed that some structures node.connections, for example, was protected by a mutex for writing, but not for reading. This is not sufficient (unless I'm missing something?)

    node.connections could ideally be protected by a RWMutex (https://golang.org/pkg/sync/#RWMutex) that can be held by many readers but only one writer...

    Let me know if you are interested in a pull request, or am I missing something clever?

    Fantastic project!! After struggling with writing a stdin/stdout port with a lot of buffering issues I'm so happy I found this!

    Not protected: https://github.com/halturin/ergonode/blob/d13e6eebb13d3f1589a62e789ce4a947fc24ed34/ergonode.go#L442

    Protected: https://github.com/halturin/ergonode/blob/c08e4a55356eb58cbe82a07a1d020153c550b003/ergonode.go#L373

  • Map string <-> ettBinary, []byte -> ettBitBinary

    Map string <-> ettBinary, []byte -> ettBitBinary

    Implement encode/decode for the types below without affecting node logic. (All tests pass)

    Mapping Types

    | Golang Type | ETF Type | | ------------- | ------------- | | string | binary | | []byte | bitBinary |

    Pros:

    • [x] Support longer strings (now max length is 4294967295, previously was 65535)

    Cons:

    • [x] []byte are now sent as ETF bit_binary with bits set to 8. (Overhead of 1 byte)
  • allow custom listener ip and port for the epmd daemon

    allow custom listener ip and port for the epmd daemon

    Hi @halturin , great fork of the original library, thank you for maintaining it.

    I am proposing adding a non-breaking change to that code allowing custom ports for the epmd daemon, in the eventuality of multiple separate node clusters (and thus, multiple epmd instances).

    It uses optional arguments, and should not change anything if those are not specified.

    Regards, silviu

  • ApplicationSpec.StartType does not take effect after configuration.

    ApplicationSpec.StartType does not take effect after configuration.

    Configure StartType to ApplicationStartTemporary but the end result is ApplicationStartTemporary

    func (da *demoApp) Load(args ...etf.Term) (gen.ApplicationSpec, error) {
        return gen.ApplicationSpec{
          Name:        "demoApp",
          Description: "Demo Applicatoin",
          Version:     "v.1.0",
          Children: []gen.ApplicationChildSpec{
          gen.ApplicationChildSpec{
              Child: createDemoSup(),
              Name:  "demoSup",
            },  
          gen.ApplicationChildSpec{
              Child: createDemoServer(),
              Name:  "demoServer",
            },  
          },  
          StartType: gen.ApplicationStartPermanent,
        }, nil 
      }
    
  • ServerProcess.CastRPC to Erlang Node does not work

    ServerProcess.CastRPC to Erlang Node does not work

    Here is the test code

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/ergo-services/ergo"
    	"github.com/ergo-services/ergo/etf"
    	"github.com/ergo-services/ergo/gen"
    	"github.com/ergo-services/ergo/node"
    )
    
    var demoSP *gen.ServerProcess
    
    type demo struct {
    	gen.Server
    }
    
    func (d *demo) Init(process *gen.ServerProcess, args ...etf.Term) error {
    	demoSP = process
    	return nil
    }
    
    func main() {
    	NodeName := "[email protected]"
    	Cookie := "cookie"
    
    	node, err := ergo.StartNode(NodeName, Cookie, node.Options{})
    	if err != nil {
    		panic(err)
    	}
    	_, err = node.Spawn("demo_server", gen.ProcessOptions{}, &demo{})
    	if err != nil {
    		panic(err)
    	}
    	remoteNode := "erl-" + node.Name()
    	fmt.Println("Start erlang node with the command below:")
    	fmt.Printf("    $ erl -name %s -setcookie %s\n\n", remoteNode, Cookie)
    	if v, e := demoSP.CallRPC(remoteNode, "persistent_term", "get", etf.Atom("test_key"), etf.Atom("default")); e == nil {
    		fmt.Printf("rpc:call erl_node = %+v\n", v)
    	} else {
    		fmt.Printf("rpc:call erl_node err = %+v\n", e)
    	}
    
    	if e := demoSP.CastRPC(remoteNode, "persistent_term", "put", etf.Atom("test_key"), etf.Atom("cast_rpc")); e == nil {
    		fmt.Printf("rpc:cast erl_node = %+v\n", "ok")
    	} else {
    		fmt.Printf("rpc:cast erl_node err = %+v\n", e)
    	}
    	time.Sleep(time.Second)
    
    	if v, e := demoSP.CallRPC(remoteNode, "persistent_term", "get", etf.Atom("test_key"), etf.Atom("default")); e == nil {
    		fmt.Printf("rpc:call erl_node = %+v\n", v)
    	} else {
    		fmt.Printf("rpc:call erl_node err = %+v\n", e)
    	}
    
    	node.Wait()
    }
    

    Open Erlang node:

     erl -name [email protected] -setcookie cookie
    

    Before fix:

    #output:
    rpc:call erl_node = default
    rpc:cast erl_node = ok
    rpc:call erl_node = default
    

    After fix:

    #output:
    rpc:call erl_node = default
    rpc:cast erl_node = ok
    rpc:call erl_node = cast_rpc
    
  • erpc:call/4 from Erlang/OTP24 to Ergo does not work

    erpc:call/4 from Erlang/OTP24 to Ergo does not work

    erpc:call/4,erpc:cast/4,rpc:call/4 are not working properly

    Here is the test code

    package main
    
    import (
    	"flag"
    	"fmt"
    
    	"github.com/ergo-services/ergo"
    	"github.com/ergo-services/ergo/etf"
    	"github.com/ergo-services/ergo/node"
    )
    
    var (
    	ServerName string
    	NodeName   string
    	Cookie     string
    )
    
    func init() {
    	flag.StringVar(&ServerName, "server", "example", "server process name")
    	flag.StringVar(&NodeName, "name", "[email protected]", "node name")
    	flag.StringVar(&Cookie, "cookie", "go_erlang_cookie", "cookie for interaction with erlang cluster")
    }
    
    func main() {
    	flag.Parse()
    
    	fmt.Println("")
    	fmt.Println("to stop press Ctrl-C")
    	fmt.Println("")
    
    	node, err := ergo.StartNode(NodeName, Cookie, node.Options{})
    	if err != nil {
    		panic(err)
    	}
    
    	testFun1 := func(a ...etf.Term) etf.Term {
    		fmt.Printf("go handle --->>> %#v\n", a)
    		return a[len(a)-1]
    	}
    	if e := node.ProvideRPC("testMod", "testFun", testFun1); e != nil {
    		panic(err)
    	}
    
    	fmt.Println("Start erlang node with the command below:")
    	fmt.Printf("    $ erl -name %s -setcookie %s\n\n", "erl-"+node.Name(), Cookie)
    
    	node.Wait()
    }
    

    Before fixing the bug

    #Erlang input 
    ([email protected])1>  erpc:call('[email protected]', testMod, testFun, [a,"hello",111]).  (Blocking)
    #Log output of Go
    2022/12/07 21:52:55 WARNING! [[email protected]] Malformed Control packet at the link with [email protected]: etf.Tuple{29, etf.Ref{Node:"[email protected]", Creation:0x638455e0, ID:[5]uint32{0x28929, 0x880c0002, 0x30bb6300, 0x0, 0x0}}, etf.Pid{Node:"[email protected]", ID:0x56, Creation:0x638455e0}, etf.Pid{Node:"[email protected]", ID:0x48, Creation:0x638455e0}, etf.Tuple{"erpc", "execute_call", 4}, etf.List{"monitor"}}
    
    #Erlang input:
    ([email protected])1>  rpc:call('[email protected]', testMod, testFun, [a,"hello",111]).  (Blocking)
    #Log output of Go
    2022/12/07 21:52:55 WARNING! [[email protected]] Malformed Control packet at the link with [email protected]: etf.Tuple{29, etf.Ref{Node:"[email protected]", Creation:0x638455e0, ID:[5]uint32{0x28929, 0x880c0002, 0x30bb6300, 0x0, 0x0}}, etf.Pid{Node:"[email protected]", ID:0x56, Creation:0x638455e0}, etf.Pid{Node:"[email protected]", ID:0x48, Creation:0x638455e0}, etf.Tuple{"erpc", "execute_call", 4}, etf.List{"monitor"}}
    
    
    #Erlang input and output:
    ([email protected])2> erpc:cast('[email protected]', testMod, testFun, [a,"hello",111]).
    ok
    #Log output of Go
    2022/12/07 21:55:58 WARNING! initialization process failed <3FA645AA.0.1012>[""] &runtime.TypeAssertionError{_interface:(*runtime._type)(0x6df520), concrete:(*runtime._type)(0x6cd940), asserted:(*runtime._type)(0x700400), missingMethod:""} at runtime.panicdottypeE[/usr/local/go/src/runtime/iface.go:262]
    

    After fixing the bug

    #Erlang input and output:
    $ erl -name [email protected] -setcookie go_erlang_cookie
    Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    Eshell V12.1.5  (abort with ^G)
    ([email protected])1>  erpc:call('[email protected]', testMod, testFun, [a,"hello",111]).
    111
    ([email protected])2> rpc:call('[email protected]', testMod, testFun, [a,"hello",111]).  
    111
    ([email protected])3> erpc:cast('[email protected]', testMod, testFun, [a,"hello",111]).
    ok
    
    #Log output of Go
    go handle --->>> []etf.Term{"a", "hello", 111}
    go handle --->>> []etf.Term{"a", "hello", 111}
    go handle --->>> []etf.Term{"a", "hello", 111}
    
  • rpc:cast/4 from Erlang/OTP24 to Ergo does not work

    rpc:cast/4 from Erlang/OTP24 to Ergo does not work

    Erlang project has rpc:cast/4 which needs to be compatible with ergo. Here is the test code

    package main
    
    import (
    	"flag"
    	"fmt"
    
    	"github.com/ergo-services/ergo"
    	"github.com/ergo-services/ergo/etf"
    	"github.com/ergo-services/ergo/node"
    )
    
    var (
    	ServerName string
    	NodeName   string
    	Cookie     string
    )
    
    func init() {
    	flag.StringVar(&ServerName, "server", "example", "server process name")
    	flag.StringVar(&NodeName, "name", "[email protected]", "node name")
    	flag.StringVar(&Cookie, "cookie", "go_erlang_cookie", "cookie for interaction with erlang cluster")
    }
    
    func main() {
    	flag.Parse()
    
    	fmt.Println("")
    	fmt.Println("to stop press Ctrl-C")
    	fmt.Println("")
    
    	node, err := ergo.StartNode(NodeName, Cookie, node.Options{})
    	if err != nil {
    		panic(err)
    	}
    
    	testFun1 := func(a ...etf.Term) etf.Term {
    		fmt.Printf("go handle --->>> %#v\n", a)
    		return a[len(a)-1]
    	}
    	if e := node.ProvideRPC("testMod", "testFun", testFun1); e != nil {
    		panic(err)
    	}
    
    	fmt.Println("Start erlang node with the command below:")
    	fmt.Printf("    $ erl -name %s -setcookie %s\n\n", "erl-"+node.Name(), Cookie)
    
    	node.Wait()
    }
    

    Before fixing the bug of rpc:call/4

    #Erlang input and output:
    ([email protected])51> rpc:cast('[email protected]', testMod, testFun, [a,"hello",111]).
    true
    
    #Log output of Go
    2022/12/07 21:23:02 WARNING! Server [rex] HandleCast: unhandled message etf.Tuple{"cast", "testMod", "testFun", etf.List{"a", "hello", 111}, etf.Pid{Node:"[email protected]", ID:0x48, Creation:0x638455dd}}
    

    After fixing the bug of rpc:call/4

    #Erlang input and output:
    ([email protected])51> rpc:cast('[email protected]', testMod, testFun, [a,"hello",111]).
    true
    
    #Log output of Go
    go handle --->>> []etf.Term{"a", "hello", 111}
    
  • Does Ergo have a plan to support OTP25/OTP26?

    Does Ergo have a plan to support OTP25/OTP26?

    Discussed in https://github.com/ergo-services/ergo/discussions/123

    Originally posted by leonlee2013 December 4, 2022 OTP-26 is scheduled to be released on May 1, 2023 If use Ergo to communicate with Erlang, Erlang cannot be upgraded to OTP25 or above, which is a limitation to Ergo's development. Is there any plan for Ergo to support OTP25/26?

Go implementation of OTP (One-time-password). SMS send async (goroutine)

Go-OTP Go realization to otp authentication Stack: Redis (Save OTP and token) Go SMS provider (Megafon as example) Schema: User send phonenumber (ex.7

Dec 14, 2021
Go based HTTP server with 2FA based on OTP (One-Time Password) manager like Google Authenticator

Go based HTTP server with 2FA based on OTP (One-Time Password) manager like Goog

Aug 21, 2022
beego 与 Ant Design Pro Vue 基础权限系统

beego Ant Design Pro Vue RBAC beego 与 Ant Design Pro Vue 基础权限系统 后端: http://beego.me ORM: gorm https://gorm.io/zh_CN/docs/ Ant Design Pro Vue文档: https:

Dec 2, 2022
A simple and lightweight library for creating, formatting, manipulating, signing, and validating JSON Web Tokens in Go.

GoJWT - JSON Web Tokens in Go GoJWT is a simple and lightweight library for creating, formatting, manipulating, signing and validating Json Web Tokens

Nov 15, 2022
Go (Golang) JWTAuth with Gin Framework

go-auth-server Go (Golang) JWTAuth with Gin Framework 1. Project Description JWT authentication Next tutorial is building secret manager 2. Run with D

Nov 14, 2021
OauthMicroservice-cassandraCluster - Implement microservice of oauth using golang and cassandra to store user tokens

implement microservice of oauth using golang and cassandra to store user tokens

Jan 24, 2022
golang csrf react example, using gorilla/mux and gorilla/mux

Demo REST backend Gorilla csrf middleware and Js frontend Use gorilla/mux and gorilla/csrf How to run open goland IDE, run middleware_test.go by click

Feb 2, 2022
Mini-framework for multiple authentication and authorization schemes
Mini-framework for multiple authentication and authorization schemes

Go authorization pattern This repository demonstrates an authorization pattern that allows multiple schemes. Demo To start the demo run the following

Dec 30, 2021
Golang Mongodb Jwt Auth Example Using Echo
Golang Mongodb Jwt Auth Example Using Echo

Golang Mongodb Jwt Auth Example Using Echo Golang Mongodb Rest Api Example Using Echo Prerequisites Golang 1.16.x Docker 19.03+ Docker Compose 1.25+ I

Nov 30, 2022
Example App written in Golang for provide AuthZ/N using JWT

RCK Auth Application Example App written in Golang for provide Authentication & Authorization using Json Web Tokens. Run with Docker / Podman Run a Po

Feb 25, 2022
A simple authentication web application in Golang (using jwt)

Simple Authentication WebApp A simple authentication web app in Go (using JWT) Routes Path Method Data /api/v1/auth/register POST {"firstname":,"lastn

Feb 6, 2022
This is a jwt for Gin framework.

JWT for Gin Framework This is a jwt useful for Gin framework. It uses jwt-go to provide a jwt encode and decode token. Usage go get github.com/wyy-go/

Jan 9, 2022
Goal: Develop a Go start auth starter without Gin framework

Goal: Develop a Go start auth starter without Gin framework and learn along the

Feb 1, 2022
Ginx - Evilginx2 - A man-in-the-middle attack framework used for phishing login credentials along with session cookies
Ginx - Evilginx2 - A man-in-the-middle attack framework used for phishing login credentials along with session cookies

evilginx2 is a man-in-the-middle attack framework used for phishing login creden

Mar 19, 2022
Oso is a batteries-included framework for building authorization in your application.

Oso What is Oso? Oso is a batteries-included framework for building authorization in your application. With Oso, you can: Model: Set up common permiss

Jan 1, 2023
an SSO and OAuth / OIDC login solution for Nginx using the auth_request module
an SSO and OAuth / OIDC login solution for Nginx using the auth_request module

Vouch Proxy An SSO solution for Nginx using the auth_request module. Vouch Proxy can protect all of your websites at once. Vouch Proxy supports many O

Jan 4, 2023
A demo using go and redis to implement a token manager

使用go-redis实现一个令牌管理器 需求描述 假设我们当前的所有服务需要一个第三方的认证,认证形式为:在发送请求的时候带上第三方颁发的令牌,该令牌具有一个时效性 第三方的令牌可以通过某个接口获取,但是该接口做了单位时间内的同一ip的请求频率的限制,因此在并发的场景下,我们需要控制令牌获取接口的频

Oct 19, 2021
Create the Provider for Zoom API and automate the creation of zoom user using terraform.

Create the Provider for Zoom API and automate the creation of zoom user using terraform.

Sep 7, 2022
A demo of authentication and authorization using jwt
A demo of authentication and authorization using jwt

Nogopy Hi, this a demo of how to use jwt for authentication in microservices Keep in mind that this is a demo of how to authenticate using jwt, we don

Nov 1, 2021