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

Ergo Framework

GitHub release Go Report Card GoDoc MIT license Build Status

Technologies and design patterns of Erlang/OTP have been proven over the years. Now in Golang. Up to x5 times faster than original Erlang/OTP in terms of network messaging. The easiest way to create an OTP-designed application in Golang.

https://ergo.services

Purpose

The goal of this project is to leverage Erlang/OTP experience with Golang performance. Ergo Framework implements DIST protocol, ETF data format and OTP design patterns gen.Server, gen.Supervisor, gen.Application which makes you able to create distributed, high performance and reliable microservice solutions having native integration with Erlang infrastructure

Features

image

  • Support Erlang 24 (including Alias and Remote Spawn features)
  • Spawn Erlang-like processes
  • Register/unregister processes with simple atom
  • gen.Server behavior support (with atomic state)
  • gen.Supervisor behavior support with all known restart strategies support
    • One For One
    • One For All
    • Rest For One
    • Simple One For One
  • gen.Application behavior support with all known starting types support
    • Permanent
    • Temporary
    • Transient
  • gen.Stage behavior support (originated from Elixir's GenStage). This is abstraction built on top of gen.Server to provide a simple way to create a distributed Producer/Consumer architecture, while automatically managing the concept of backpressure. This implementation is fully compatible with Elixir's GenStage. Example is here examples/genstage or just run go run ./examples/genstage to see it in action
  • gen.Saga behavior support. It implements Saga design pattern - a sequence of transactions that updates each service state and publishes the result (or cancels the transaction or triggers the next transaction step). gen.Saga also provides a feature of interim results (can be used as transaction progress or as a part of pipeline processing), time deadline (to limit transaction lifespan), two-phase commit (to make distributed transaction atomic). Here is example examples/gensaga.
  • Connect to (accept connection from) any Erlang node within a cluster
  • Making sync request ServerProcess.Call, async - ServerProcess.Cast or Process.Send in fashion of gen_server:call, gen_server:cast, erlang:send accordingly
  • Monitor processes/nodes
    • local -> local
    • local -> remote
    • remote -> local
  • Link processes
    • local <-> local
    • local <-> remote
    • remote <-> local
  • RPC callbacks support
  • embedded EPMD (in order to get rid of erlang' dependencies)
  • Experimental observer support
  • Unmarshalling terms into the struct using etf.TermIntoStruct, etf.TermProplistIntoStruct or to the string using etf.TermToString
  • Custom marshaling/unmarshaling via Marshal and Unmarshal interfaces
  • Encryption (TLS 1.3) support (including autogenerating self-signed certificates)
  • Tested and confirmed support Windows, Darwin (MacOS), Linux, FreeBSD.

Requirements

  • Go 1.15.x and above

Versioning

Golang introduced v2 rule a while ago to solve complicated dependency issues. We found this solution very controversial and there is still a lot of discussion around it. So, we decided to keep the old way for the versioning, but have to use the git tag versioning with v1 as a major version (due to "v2 rule" restrictions) . As a starting point for the v2.0.0 we use git tag v1.999.200. Since now, the only "patch version" will be increased for the next releases (e.g. v2.0.1 will be tagged in git as v.1.999.201 and so on, but never be above git tag v1.999 until the moment when Golang developers change the versioning approach)

Changelog

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

v2.0.0 2021-10-12 [tag version v1.999.200]

  • Added support of Erlang/OTP 24 (including Alias feature and Remote Spawn introduced in Erlang/OTP 23)
  • Important: This release includes refined API (without backward compatibility) for a more convenient way to create OTP-designed microservices. Make sure to update your code.
  • Important: Project repository has been moved to https://github.com/ergo-services/ergo. It is still available on the old URL https://github.com/halturin/ergo and GitHub will redirect all requests to the new one (thanks to GitHub for this feature).
  • Introduced new behavior gen.Saga. It implements Saga design pattern - a sequence of transactions that updates each service state and publishes the result (or cancels the transaction or triggers the next transaction step). gen.Saga also provides a feature of interim results (can be used as transaction progress or as a part of pipeline processing), time deadline (to limit transaction lifespan), two-phase commit (to make distributed transaction atomic). Here is example examples/gensaga.
  • Introduced new methods Process.Direct and Process.DirectWithTimeout to make direct request to the actor (gen.Server or inherited object). If an actor has no implementation of HandleDirect callback it returns ErrUnsupportedRequest as a error.
  • Introduced new callback HandleDirect in the gen.Server interface as a handler for requests made by Process.Direct or Process.DirectWithTimeout. It should be easy to interact with actors from outside.
  • Introduced new types intended to be used to interact with Erlang/Elixir
    • etf.ListImproper to support improper lists like [a|b] (a cons cell).
    • etf.String (an alias for the Golang string) encodes as a binary in order to support Elixir string type (which is binary() type)
    • etf.Charlist (an alias for the Golang string) encodes as a list of chars []rune in order to support Erlang string type (which is charlist() type)
  • Introduced new methods Node.ProvideRemoteSpawn, Node.RevokeRemoteSpawn, Process.RemoteSpawn.
  • Introduced new interfaces Marshaler (method MarshalETF) and Unmarshaler (method UnmarshalETF) for the custom encoding/decoding data.
  • Improved performance for the local messaging (up to 3 times for some cases)
  • Added example examples/http to demonsrate how HTTP server can be integrated into the Ergo node.
  • Added example examples/gendemo - how to create a custom behavior (design pattern) on top of the gen.Server. Take inspiration from the gen/stage.go or gen/saga.go design patterns.
  • Added support FreeBSD, OpenBSD, NetBSD, DragonFly.
  • Fixed RPC issue #45
  • Fixed internal timer issue #48
  • Fixed memory leaks #53
  • Fixed double panic issue #52
  • Fixed Atom Cache race conditioned issue #54
  • Fixed ETF encoder issues #64 #66

Benchmarks

Here is simple EndToEnd test demonstrates performance of messaging subsystem

Sequential Process.Call using two processes running on a single and two nodes

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

❯❯❯❯ go test -bench=NodeSequential -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/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/ergo-services/ergo	120.720s

it means Ergo Framework provides around 25.000 sync requests per second via localhost for simple data and around 4Gbit/sec for 1MB messages

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

Hardware: workstation with AMD Ryzen Threadripper 3970X (64) @ 3.700GHz

❯❯❯❯ go test -bench=NodeParallel -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeParallel-64                 4922430              2440 ns/op
BenchmarkNodeParallelSingleNode-64      16293586               810.0 ns/op
PASS
ok      github.com/ergo-services/ergo/tests  29.596s

these numbers show almost 500.000 sync requests per second for the network messaging via localhost and 1.600.000 sync requests per second for the local messaging (within a node).

Ergo Framework vs original Erlang/OTP

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

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.

Observer

It's a standard Erlang tool. Observer is a graphical tool for observing the characteristics of Erlang systems. The tool Observer displays system information, application supervisor trees, process information.

Here you can see this feature in action using one of the examples:

observer demo

Examples

Code below is a simple implementation of gen.Server pattern examples/simple

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"
)

// simple implementation of Server
type simple struct {
	gen.Server
}

func (s *simple) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
	value := message.(int)
	fmt.Printf("HandleInfo: %#v \n", message)
	if value > 104 {
		return gen.ServerStatusStop
	}
	// sending message with delay
	process.SendAfter(process.Self(), value+1, time.Duration(1*time.Second))
	return gen.ServerStatusOK
}

func main() {
	// create a new node
	node, _ := ergo.StartNode("node@localhost", "cookies", node.Options{})

	// spawn a new process of gen.Server
	process, _ := node.Spawn("gs1", gen.ProcessOptions{}, &simple{})

	// send a message to itself
	process.Send(process.Self(), 100)

	// wait for the process termination.
	process.Wait()
	fmt.Println("exited")
	node.Stop()
}

here is output of this code

$ go run ./examples/simple
HandleInfo: 100
HandleInfo: 101
HandleInfo: 102
HandleInfo: 103
HandleInfo: 104
HandleInfo: 105
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. The spawning process must have "pg2" as a process name:

type Pg2GenServer struct {
    gen.Server
}

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

Development and debugging

There are options already defined that you might want to use

  • -ergo.trace - enable extended debug info
  • -ergo.norecover - disable panic catching

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

To check test coverage:

go test -coverprofile=cover.out ./...
go tool cover -html=cover.out -o coverage.html

To run tests with cleaned test cache:

go vet
go clean -testcache
go test -v ./...

To run benchmarks:

go test -bench=Node -run=X -benchmem

Companies are using Ergo Framework

Kaspersky RingCentral LilithGames

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

Commercial support

please, visit https://ergo.services for more information

Owner
Ergo Services
Cloud platform to enrich services made with Ergo Framework. We don’t need your infra. We provide you enablers.
Ergo Services
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?

A standard library for microservices.

Go kit Go kit is a programming toolkit for building microservices (or elegant monoliths) in Go. We solve common problems in distributed systems and ap

Jan 2, 2023
A Distributed Content Licensing Framework (DCLF) using Hyperledger Fabric permissioned blockchain.

A Distributed Content Licensing Framework (DCLF) using Hyperledger Fabric permissioned blockchain.

Nov 4, 2022
Go Micro is a framework for distributed systems development

Go Micro Go Micro is a framework for distributed systems development. Overview Go Micro provides the core requirements for distributed systems develop

Jan 8, 2023
Skynet is a framework for distributed services in Go.
Skynet is a framework for distributed services in Go.

##Introduction Skynet is a communication protocol for building massively distributed apps in Go. It is not constrained to Go, so it will lend itself n

Nov 18, 2022
Cross-platform grid-based user interface framework.

Gruid The gruid module provides packages for easily building grid-based applications in Go. The library abstracts rendering and input for different pl

Nov 23, 2022
a dynamic configuration framework used in distributed system
a dynamic configuration framework used in distributed system

go-archaius This is a light weight configuration management framework which helps to manage configurations in distributed system The main objective of

Dec 9, 2022
Go Micro is a standalone framework for distributed systems development

Go Micro Go Micro is a framework for distributed systems development. Overview Go Micro provides the core requirements for distributed systems develop

Dec 31, 2022
An actor framework for Go

gosiris is an actor framework for Golang. Features Manage a hierarchy of actors (each actor has its own: state, behavior, mailbox, child actors) Deplo

Dec 28, 2022
Tarmac is a unique framework designed for the next generation of distributed systems
Tarmac is a unique framework designed for the next generation of distributed systems

Framework for building distributed services with Web Assembly

Dec 31, 2022
Dynatomic is a library for using dynamodb as an atomic counter

Dynatomic Dynatomic is a library for using dynamodb as an atomic counter Dynatomic Motivation Usage Development Contributing Motivation The dynatomic

Sep 26, 2022
Simplified distributed locking implementation using Redis

redislock Simplified distributed locking implementation using Redis. For more information, please see examples. Examples import ( "fmt" "time"

Dec 24, 2022
A distributed lock service in Go using etcd

locker A distributed lock service client for etcd. What? Why? A distributed lock service is somewhat self-explanatory. Locking (mutexes) as a service

Sep 27, 2022
Lockgate is a cross-platform locking library for Go with distributed locks using Kubernetes or lockgate HTTP lock server as well as the OS file locks support.

Lockgate Lockgate is a locking library for Go. Classical interface: 2 types of locks: shared and exclusive; 2 modes of locking: blocking and non-block

Dec 16, 2022
Rink is a "distributed sticky ranked ring" using etcd.

Rink is a "distributed sticky ranked ring" using etcd. A rink provides role scheduling across distributed processes, with each role only assigned

Dec 5, 2022
This is a comprehensive system that simulate multiple servers’ consensus behavior at local machine using multi-process deployment.

Raft simulator with Golang This project is a simulator for the Raft consensus protocol. It uses HTTP for inter-server communication, and a job schedul

Jan 30, 2022
Golang client library for adding support for interacting and monitoring Celery workers, tasks and events.

Celeriac Golang client library for adding support for interacting and monitoring Celery workers and tasks. It provides functionality to place tasks on

Oct 28, 2022
Dec 27, 2022
Simple, fast and scalable golang rpc library for high load

gorpc Simple, fast and scalable golang RPC library for high load and microservices. Gorpc provides the following features useful for highly loaded pro

Dec 19, 2022
dht is used by anacrolix/torrent, and is intended for use as a library in other projects both torrent related and otherwise

dht Installation Install the library package with go get github.com/anacrolix/dht, or the provided cmds with go get github.com/anacrolix/dht/cmd/....

Dec 28, 2022