Giter Site home page Giter Site logo

ergo-services / ergo Goto Github PK

View Code? Open in Web Editor NEW
2.7K 53.0 127.0 2.02 MB

An actor-based Framework with network transparency for creating event-driven architecture in Golang. Inspired by Erlang. Zero dependencies.

Home Page: https://ergo.services

License: MIT License

Go 100.00%
erlang golang elixir supervisor worker otp otp-applications distributed-systems framework microservice

ergo's Introduction

Ergo Framework

GoDoc MIT license Telegram Community Discord Community Twitter

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

Cloud

Distributed Cloud is coming. With Ergo Framework you can join your services into a single cluster with transparent networking using our Cloud Overlay Network where they can connect to each other smoothly, no matter where they run - AWS, Azure or GCP, or anywhere else. All these connections are secured with end-to-end encryption. Read more in this article https://blog.ergo.services/cloud-overlay-network-3a133d47efe5.

Quick start

First, you need to install the boilerplate code generation tool ergo - https://github.com/ergo-services/tools using command below

go install ergo.services/tools/ergo@latest

And then, you can create your project with just one command. Here is example:

Supervision tree

mynode
|- myapp
|   |
|    `- mysup
|        |
|         `- myactor
|- myweb
`- myactor2

To generate project for this design use the following command:

ergo -init MyNode -with-app MyApp -with-sup MyApp:MySup -with-actor MySup:MyActor -with-web "MyWeb{port:8000,handlers:3}" -with-actor MyActor2

as a result you will get generated project:

   mynode/
   |-- apps/
   |   `-- myapp/
   |       |-- myactor.go
   |       |-- myapp.go
   |       `-- mysup.go
   |-- cmd/
   |   |-- myactor2.go
   |   |-- mynode.go
   |   |-- myweb.go
   |   `-- myweb_handler.go
   |-- README.md
   |-- go.mod
   `-- go.sum

to try it:

$ cd mynode
$ go run ./cmd/

You may also read our article about this tool with a great example https://blog.ergo.services/quick-start-1094d56d4e2

Features

image

  • Support Erlang 25 - allows you connect your node to (and accept connection from) any Erlang/Elixir node within a cluster
  • Spawn Erlang-like processes
  • Register/unregister processes with simple atom
  • Set of ready-to-use disign patterns (behaviors)
    • gen.Server behavior with atomic state and Erlang's gen_server support to make sync request ServerProcess.Call, async - ServerProcess.Cast or Process.Send in fashion of gen_server:call, gen_server:cast, erlang:send accordingly
    • gen.Supervisor behavior with all known restart strategies (One For One, One For All, Rest For One, Simple One For One)
    • gen.Application behavior with all known starting types (Permanent, Temporary, Transient)
    • gen.Pool a basic design pattern with a pool of workers. All messages/requests received by the pool process are forwarded to the workers using the "Round Robin" algorithm. The worker process is automatically restarting on termination
    • gen.TCP - socket acceptor pool for TCP protocols. This behavior aims to provide everything you need to accept TCP connections and process packets with a small code base and low latency while being easy to use.
    • gen.UDP - acceptor pool for UDP protocols. This behavior provides the same feature set as TCP but for handling UDP packets using pool of handlers
    • gen.Web - Web API Gateway behavior. This behavior allows you to listen HTTP port and handle HTTP-request using pool of workers.
    • 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.
    • gen.Raft behavior support. It's improved implementation of Raft consensus algorithm. The key improvement is using quorum under the hood to manage the leader election process and make the Raft cluster more reliable. This implementation supports quorums of 3, 5, 7, 9, or 11 quorum members. Here is an example of this feature examples/genraft
  • Monitor processes/nodes, local/remote with Erlang support
  • Link processes local/remote with Erlang support
  • embedded EPMD (in order to get rid of erlang' dependencies) with Erlang support
  • Unmarshalling terms into the struct using etf.TermIntoStruct, etf.TermProplistIntoStruct or to the string using etf.TermToString including custom marshaling/unmarshaling via Marshal and Unmarshal interfaces. But it's highly recommended to use etf.RegisterType so you will be receiving messages in a native Golang-type
  • Encryption (TLS 1.3) support (including autogenerating self-signed certificates)
  • Compression support (with customization of compression level and threshold). It can be configured for the node or a particular process.
  • Proxy support with end-to-end encryption, includeing compression/fragmentation/linking/monitoring features.
  • Tested and confirmed support Windows, Darwin (MacOS), Linux, FreeBSD.
  • Zero dependencies. All features are implemented using the standard Golang library.

Requirements

  • Go 1.17.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 with v1 as a major version (due to "v2 rule" restrictions). Since now we use git tag pattern 1.999.XYZ where X - major number, Y - minor, Z - patch version.

Changelog

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

v2.2.4 2023-05-01 [tag version v1.999.224]

This release includes fixes:

  • Fixed incorrect handling of gen.SupervisorStrategyRestartTransient restart strategy in gen.Supervisor
  • Fixed missing ServerBehavior in [gen.Pool, gen.Raft, gen.Saga, gen.Stage, gen.TCP, gen.UDP, gen.Web] behavior interfaces
  • Introduced the new tool for boilerplate code generation - ergo https://github.com/ergo-services/tools. You may read more information about this tool in our article with a great example https://blog.ergo.services/quick-start-1094d56d4e2

Benchmarks

Here is simple EndToEnd test demonstrates performance of messaging subsystem

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                 4738918              2532 ns/op
BenchmarkNodeParallelSingleNode-64      100000000              429.8 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 10.000.000 sync requests per second for the local messaging (within a node).

Compression

This benchmark shows the performance of compression for sending 1MB message between two nodes (via a network).

❯❯❯❯ go test -bench=NodeCompression -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeCompressionDisabled1MBempty-64         2400           4957483 ns/op
BenchmarkNodeCompressionEnabled1MBempty-64          5769           2088051 ns/op
BenchmarkNodeCompressionEnabled1MBstring-64         5202           2077099 ns/op
PASS
ok      github.com/ergo-services/ergo/tests     56.708s

It demonstrates more than 2 times improvement.

Proxy

This benchmark demonstrates how proxy feature and e2e encryption impact a messaging performance.

❯❯❯❯ go test -bench=NodeProxy -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeProxy_NodeA_to_NodeC_direct_Message_1KB-64                     1908477       6337 ns/op
BenchmarkNodeProxy_NodeA_to_NodeC_via_NodeB_Message_1KB-64                  1700984       7062 ns/op
BenchmarkNodeProxy_NodeA_to_NodeC_via_NodeB_Message_1KB_Encrypted-64        1271125       9410 ns/op
PASS
ok      github.com/ergo-services/ergo/tests     45.649s

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.

Examples

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

package main

import (
	"fmt"
	"time"

	"github.com/ergo-services/ergo/etf"
	"github.com/ergo-services/ergo/gen"
)

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 1 second
	fmt.Println("increase this value by 1 and send it to itself again")
	process.SendAfter(process.Self(), value+1, time.Second)
	return gen.ServerStatusOK
}

here is output of this code

$ go run ./examples/simple
HandleInfo: 100
HandleInfo: 101
HandleInfo: 102
HandleInfo: 103
HandleInfo: 104
HandleInfo: 105
exited

See https://github.com/ergo-services/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 debug info (logging via lib.Log(...))
  • -ergo.debug - enable extended debug info (logging via lib.Log(...) and lib.Warning(...))
  • -ergo.norecover - disable panic catching
  • -ergo.warning - enable/disable warnings (logging via lib.Warning(...). Default: enable)

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, contact [email protected] for more information

ergo's People

Contributors

alexkovalevych avatar dmitrivereshchagin avatar ftrvxmtrx avatar halturin avatar juise avatar larrouse2015 avatar mdevilliers avatar mytholog avatar rutaka-n avatar zert avatar

Stargazers

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

Watchers

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

ergo's Issues

Second CallRPC method is stuck when it is called against remote erlang node

Describe the bug
I can't do CallRPC to remote erlang node more than one time.
The second CallRPC is stuck due to this line in the code:

sp.callbackWaitReply <- &ref

To Reproduce
Steps to reproduce the behavior:

Probably, I'm doing something wrong, but there is no documentation for this case.

My code:

func CreateNewNode() *ErlangHelper {
	eh := new(ErlangHelper)
	opts := node2.Options{}

	// Initialize new node with given name, cookie, listening port range and epmd port
	node, err := ergo.StartNode(configuration.Cfg.SelfNodeFullName, configuration.Cfg.Cookie, opts)
	if err != nil {
		panic(err)
	}
	// Initialize new instance of demoGenServ structure which implements Process behaviour
	_, _ = node.Spawn("exporter", gen.ProcessOptions{}, eh)
	return eh
}

type ErlangHelper struct {
	gen.Server
	ErlangProcess
}

type ErlangProcess struct {
	*gen.ServerProcess
}

func (eh *ErlangHelper) Init(process *gen.ServerProcess, args ...etf.Term) error {
	demo := &ErlangProcess{
		ServerProcess: process,
	}
	// do not inherit parent State
	demo.State = nil

	//if err := process.Behavior().(DemoBehavior).InitDemo(demo, args...); err != nil {
	//	return err
	//}
	process.State = demo
	eh.ServerProcess = process
	return nil
}

func (eh *ErlangHelper) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
	return gen.ServerStatusOK
}

func (eh *ErlangHelper) CallRPC(node string, module string, f string, args ...etf.Term) etf.Term {
	log.Debugf("Calling RPC to %v, module %v, func %v, args %v", node, module, f, args)
	res, err := eh.ServerProcess.CallRPCWithTimeout(1, node, module, f, args...)
	if err != nil {
		panic(err)
	}
	return res
}

When I do CallRPC first time, it works, I receive information from an erlang (23) node.

But the second one is stuck, because no one reads sp.callbackWaitReply channel in the second call (I don't know why).

The only one thins that I need is send RPCs to another erlang nodes.

What I've missed? Thanks.

Expected behavior
RPC calls work

Screenshots

Environment (please complete the following information):

  • Arch: aarch64
  • OS: Mac
  • Framework Version 2.2.0
  • 10

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.

Race condition caused segfault

Here is details:

[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x5639bf]

goroutine 39 [running]:
example/vendor/github.com/halturin/ergonode/dist.(*NodeDesc).GetRemoteName(...)
    /home/taras/devel/demo/example/vendor/github.com/halturin/ergonode/dist/dist.go:313
example/vendor/github.com/halturin/ergonode.(*Node).run.func2(0xc000104660, 0x5ee6e0, 0xc000114040, 0xc0000ce000, 0xc000104600)
    /home/taras/devel/demo/example/vendor/github.com/halturin/ergonode/ergonode.go:277 +0x17f
created by example/vendor/github.com/halturin/ergonode.(*Node).run
    /home/taras/devel/demo/example/vendor/github.com/halturin/ergonode/ergonode.go:266 +0x10c

these two goroutines in race condition after the link has been closed.

image

getting remote name has no checking for the 'nil' value

func (nd *NodeDesc) GetRemoteName() etf.Atom {
	return etf.Atom(nd.remote.Name)
}

Ergonode stuck after first message received

Having ergonode running in kubernetes cluster as docker container from image:

FROM golang:1.11.5-alpine3.9 as builder

ARG APP_NAME

WORKDIR /${GOPATH}/src/${APP_NAME}

ADD . .

RUN apk add git curl

RUN curl -fsSL -o /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 && chmod +x /usr/local/bin/dep

RUN dep ensure -vendor-only

RUN go build -o /src/${APP_NAME}_build main.go

FROM erlang:21.2.5-alpine

RUN apk update && apk add --no-cache \
  ncurses-libs \
  zlib \
  ca-certificates \
  openssl \
  bash

RUN rm -rf /var/cache/apk/*

WORKDIR /root

ARG APP_NAME

COPY --from=builder /src/${APP_NAME}_build .

CMD ["./${APP_NAME}_build"]

Successfully connected to it from an elixir node, but after the first message sent, it's stuck:

iex([email protected])34> GenServer.call({:mongo_transaction, :'[email protected]'}, :pid)
#PID<35993.4.1>
iex([email protected])35> GenServer.call({:mongo_transaction, :'[email protected]'}, :pid)
** (exit) exited in: GenServer.call({:mongo_transaction, :"[email protected]"}, :pid, 5000)
    ** (EXIT) time out
    (elixir) lib/gen_server.ex:989: GenServer.call/3

After some time (about minute or something) golang node responses again, but again stuck after the first message.

So, i guess the issue is reproducible if elixir node and golang node have different epmd servers (since each is running in a separate kubernetes pod), locally everything works fine.

i found rpc:call from erlang to ergo is not working

Discussed in #114

Originally posted by xiaodoudou12 November 18, 2022
my OTP version is Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit].
golang version is go1.18.ergo version is v1.999.211.
i use https://github.com/ergo-services/ergo/blob/v1.999.211/examples/genserver/server.go this to test.
gen_server:cast and gen_server:call is ok,but rpc:call failed.
https://github.com/ergo-services/ergo/blob/v1.999.211/proto/dist/proto.go in this file

			case distProtoSPAWN_REQUEST:
				// {29, ReqId, From, GroupLeader, {Module, Function, Arity}, OptList}
				lib.Log("[%s] CONTROL SPAWN_REQUEST [from %s]: %#v", dc.nodename, dc.peername, message.control)
				if message.proxy != nil && message.proxy.session.NodeFlags.EnableRemoteSpawn == false {
					// we didn't allow this feature. proxy session will be closed due to
					// this violation of the contract
					return node.ErrPeerUnsupported
				}
				registerName := ""
				for _, option := range t.Element(6).(etf.List) {
					name, ok := option.(etf.Tuple)
					if !ok || len(name) != 2 {
						return fmt.Errorf("malformed spawn request")
					}
					switch name.Element(1) {
					case etf.Atom("name"):
						registerName = string(name.Element(2).(etf.Atom))
					}
				}

				from := t.Element(3).(etf.Pid)
				ref := t.Element(2).(etf.Ref)

				mfa := t.Element(5).(etf.Tuple)
				module := mfa.Element(1).(etf.Atom)
				function := mfa.Element(2).(etf.Atom)
				var args etf.List
				if str, ok := message.payload.(string); !ok {
					args, _ = message.payload.(etf.List)
				} else {
					// stupid Erlang's strings :). [1,2,3,4,5] sends as a string.
					// args can't be anything but etf.List.
					for i := range []byte(str) {
						args = append(args, str[i])
					}
				}

the message value is

&dist.distMessage{
control:etf.Tuple{29, etf.Ref{Node:"[email protected]", Creation:0x636ca74d, ID:[5]uint32{0x143cd, 0x843c0001, 0x9a32d1ab, 0x0, 0x0}}, etf.Pid{Node:"[email protected]", ID:0x55, Creation:0x636ca74d}, etf.Pid{Node:"[email protected]", ID:0x47, Creation:0x636ca74d}, etf.Tuple{"erpc", "execute_call", 4}, etf.List{"monitor"}}, payload:etf.List{etf.Ref{Node:"[email protected]", Creation:0x636ca74d, ID:[5]uint32{0x143cb, 0x843c0001, 0x9a32d1ab, 0x0, 0x0}}, "rpc", "request", etf.List{"hello", 3.14}}, proxy:(*dist.proxySession)(nil)}

t.Element(6). is etf.List{"monitor"}, and len(name) != 2, so it failed
i found registerName should be erpc, i don't know is conrrect.

and node/core.go in function func (c *core) RouteSpawnRequest

		// spawn new process
		process_opts := processOptions{}
		process_opts.Env = map[gen.EnvKey]interface{}{EnvKeyRemoteSpawn: request.Options}
		process, err_spawn := c.spawn(request.Options.Name, process_opts, b.Behavior, args...)

process_opts.Env show init add "ergo:RemoteSpawnRequest" like this

process_opts.Env = map[gen.EnvKey]interface{}{EnvKeyRemoteSpawn: request.Options, "ergo:RemoteSpawnRequest":request}

because in erlang/rex.go in func (e *erpc) HandleCast will need process.Env("ergo:RemoteSpawnRequest")

func (e *erpc) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
	lib.Log("ERPC [%v]: HandleCast: %#v", process.Self(), message)
	mfa := message.(erpcMFA)
	rsr := process.Env("ergo:RemoteSpawnRequest").(gen.RemoteSpawnRequest)

so that,rpc:call from erlang to ergo work ok,but rpc:cast not work also.

in erlang i use like this
([email protected])1> rpc:call('[email protected]', rpc, request, [hello, 3.14]).
[hello,3.14]

Panic when sending time.Time

Somewhere in this library will panic when sending a golang time.Time:

panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

The golang reflect documentation suggests to use reflect.Value.CanInterface() before calling reflect.Value.Interface(). If !v.CanInterface(), we could use reflect.Value.String() instead or the unexported access technique in the below link.

Client side api to a Go based GenServer/Application: GenServer.call and GenServer.cast?

Hello, I just bumped into this project. Having some experience with Erlang/Elixir and also needing Go, this project would bridge a knowledge gap for sure.

When looking at the examples, I noticed they all show the receiving end of a message, which is processed by a HandleCall or HandleCast. In Erlang/Elixir, you typically write client side wrappers, meaning functions which call into GenServer.call or GenServer.cast.

Does this project also provide the Go based counterpart of the call and cast functions to send messages to the mailbox of a process?

how to use simple_one_for_one under supervisor tree

Hi:
I'm writing something which use supervisor:start_child to dynamic create worker process.
But my code show's me these processes share same struct which I just make for gen.Server behavior.
Maybe I should write some creater code with method to create the behavior struct , but I donnot know how to do this with supervisor.
Is there some demo code show me how to do this ?
Or I just can do this with spawn process which never under supervisor tree ?

Unittest `TestDecodeFragment` occasionally fails

Describe the bug

All unittests run stabil, only TestDecodeFragment occasionally fails.

To Reproduce

image

https://github.com/ergo-services/ergo/actions/runs/3106223372/jobs/5032829002#step:4:242

Expected behavior

Should be success in stable manner.

Screenshots

seq 10 | xargs -Iz go test -count 1 -run ^TestDecodeFragment$ github.com/ergo-services/ergo/proto/dist
--- FAIL: TestDecodeFragment (0.36s)
    proto_test.go:288: got nil result
FAIL
FAIL    github.com/ergo-services/ergo/proto/dist        0.365s
FAIL
ok      github.com/ergo-services/ergo/proto/dist        0.367s
ok      github.com/ergo-services/ergo/proto/dist        0.367s
ok      github.com/ergo-services/ergo/proto/dist        0.369s
ok      github.com/ergo-services/ergo/proto/dist        0.372s
--- FAIL: TestDecodeFragment (0.36s)
    proto_test.go:288: got nil result
FAIL
FAIL    github.com/ergo-services/ergo/proto/dist        0.365s
FAIL
ok      github.com/ergo-services/ergo/proto/dist        0.368s
ok      github.com/ergo-services/ergo/proto/dist        0.368s
--- FAIL: TestDecodeFragment (0.36s)
    proto_test.go:288: got nil result
FAIL
FAIL    github.com/ergo-services/ergo/proto/dist        0.367s
FAIL
--- FAIL: TestDecodeFragment (0.36s)
    proto_test.go:288: got nil result
FAIL
FAIL    github.com/ergo-services/ergo/proto/dist        0.366s
FAIL

Environment (please complete the following information):

	Operating System
	Ubuntu
	20.0.5
	LTS
  
  go env
	GO111MODULE=""
	GOARCH="amd64"
	GOBIN=""
	GOCACHE="/home/runner/.cache/go-build"
	GOENV="/home/runner/.config/go/env"
	GOEXE=""
	GOEXPERIMENT=""
	GOFLAGS=""
	GOHOSTARCH="amd64"
	GOHOSTOS="linux"
	GOINSECURE=""
	GOMODCACHE="/home/runner/go/pkg/mod"
	GONOPROXY=""
	GONOSUMDB=""
	GOOS="linux"
	GOPATH="/home/runner/go"
	GOPRIVATE=""
	GOPROXY="https://proxy.golang.org,direct"
	GOROOT="/opt/hostedtoolcache/go/1.17.13/x64"
	GOSUMDB="sum.golang.org"
	GOTMPDIR=""
	GOTOOLDIR="/opt/hostedtoolcache/go/1.17.13/x64/pkg/tool/linux_amd64"
	GOVCS=""
	GOVERSION="go1.17.13"
	GCCGO="gccgo"
	AR="ar"
	CC="gcc"
	CXX="g++"
	CGO_ENABLED="1"
	GOMOD="/home/runner/work/ergo/ergo/go.mod"
	CGO_CFLAGS="-g -O2"
	CGO_CPPFLAGS=""
	CGO_CXXFLAGS="-g -O2"
	CGO_FFLAGS="-g -O2"
	CGO_LDFLAGS="-g -O2"
	PKG_CONFIG="pkg-config"
	GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1316604600=/tmp/go-build -gno-record-gcc-switches"

connect to erlang 20 failed

erl -name [email protected] --setcookie 123
Erlang/OTP 20 [erts-9.1.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Eshell V9.1.2  (abort with ^G)
([email protected])2> net_adm:ping('[email protected]').
pang
([email protected])3>
=ERROR REPORT==== 14-Oct-2017::21:45:42 ===
** '[email protected]': Connection attempt to node '[email protected]' aborted since it cannot handle ["UTF8_ATOMS"].**

([email protected])3> erlang:system_info(otp_release).
"20"

some problems

Several problems were found:

  1. All the mailbox messages in the process are processed by coroutines. At present, there is a situation where the main process crashes unexpectedly, but the messages in the Mailbox can be processed in additional ways.

pid

  1. Multiple Ergo nodes communicate, and some nodes restart back and forth. Accidentally, after the restart, nodes cannot connect to each other, and Erlang nodes cannot ping Ergo nodes

freeze serving link between nodes for some period of time in corner case (under huge highload)

making sync call request via Procecc.Call can lock the serving link with the node we calling to.

the problem is in the way of handling incoming messages

p.mailBox <- etf.Tuple{from, message}

if some process (GenServer) is making outgoing sync request Process.Call within handle_[cast|call|info] it locks the internal mutex

until it receives the answer.

How it can happen in the details...

  1. start GenServer (A) with the default size of the mailbox
  2. send some message to this A
  3. in handle_info make a call to some process outside of this node. let it be B
  4. make some pause on B side with the answer
  5. flood A with any message (as we did it on the second step) just to overflow the mailbox (has a limit DefaultProcessMailboxSize = 100 )

now we got a block of writing to the mailbox due to the limit of mailbox size (buffered channel length). since this moment we are not able to receive and handle the answer we are waiting for on step 3. Usually, this sync call is limited by the timeout (default is 5 seconds, but can be customized via CallWithTimeout). It means during this period serving this link (between these nodes) is frozen.

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

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
}

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.

Erlang 23 support

Hello!

Do you have a plan to add support for Erlang 23?

Here is an error from Erlang 23 node when I try to call RPC on it from go:

➜ ~ erl -sname demo -setcookie 123
Erlang/OTP 23 [erts-11.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

Eshell V11.2.1  (abort with ^G)
(demo@MBP-Alexandr)1> =ERROR REPORT==== 11-May-2021::09:24:34.078882 ===
** 'demo@MBP-Alexandr': Connection attempt from node 'test@MBP-Alexandr' rejected since it cannot handle ["BIG_CREATION"].**

demo run error

$ pwd
/Users/shiqi/go/src/github.com/ergo/examples/genserver

$ go run demoGenServer.go
Init: args [] 
Run erl shell:
erl -name [email protected] -setcookie 123
-----Examples that can be tried from 'erl'-shell
gen_server:cast({example,'[email protected]'}, stop).
gen_server:call({example,'[email protected]'}, hello).

when i run erl shell

$ erl -name [email protected] -setcookie 123
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

Eshell V11.1.3  (abort with ^G)
([email protected])1> gen_server:cast({example,'[email protected]'}, stop).
ok
([email protected])2> =ERROR REPORT==== 21-May-2021::17:45:03.682489 ===
** '[email protected]': Connection attempt to node '[email protected]' aborted since it cannot handle ["BIG_CREATION"].**

gen_server:call({example,'[email protected]'}, hello).
=ERROR REPORT==== 21-May-2021::17:45:12.825623 ===
** '[email protected]': Connection attempt to node '[email protected]' aborted since it cannot handle ["BIG_CREATION"].**

** exception exit: {{nodedown,'[email protected]'},
                    {gen_server,call,[{example,'[email protected]'},hello]}}
     in function  gen_server:call/2 (gen_server.erl, line 238)
([email protected])3> 

go version 1.15.1

Migrate to go module

It is easier to use go modules which was introduced in go 1.11 for development and working with dependecies in projects.
Please take a look: https://github.com/golang/go/wiki/Modules
It would be greate to migrate this project to go modules
What do you think about that?

Have a nice day!

Elixir GenStage crashes when receiving demand from Ergo

Describe the bug
Elixir GenStage crashes when receiving demand from Ergo.
Error:

[error] GenServer Producer terminating
** (FunctionClauseError) no function clause matching in GenStage.maybe_producer_cancel/2

To Reproduce
I made a small repo with the reproduction steps:
https://github.com/okkdev/ergo_elixir_genstage_issue

Expected behavior
Have the Elixir Producer handle the demand and Ergo do the work.

Environment (please complete the following information):

  • Arch: arm64
  • OS: macOS
  • Framework Version v1.999.210
  • Number of CPU: 10

Observer doesn't connect to demo application

Hello,
I was just testing ergo framework and tried Observebility from erlang tools but it failed.

The followings are the environment information about Erlang and OTP version.
I am not a seasoned Erlang programmer but I think the version should be okay.

Any Idea what am I doing wrong?

image

GenStage available?

Hello, you don't have a GenStage implementation available for the ergo project? This would be a valuable addition.

Ergo receives DIST packet along with the HANDSHAKE final packet.

Discussed in #115

Originally posted by xiaodoudou12 November 18, 2022
my OTP version is Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit].
golang version is go1.18.ergo version is v1.999.211.
https://github.com/ergo-services/ergo/blob/v1.999.211/proto/dist/handshake.go:247

			case 'a':
				// 'a' + 16 (digest)
				if len(buffer) != 17 {
					return details, fmt.Errorf("malformed handshake ('a' length of digest)")
				}

				// 'a' + 16 (digest)
				digest := genDigest(dh.challenge, cookie)
				if bytes.Compare(buffer[1:17], digest) != 0 {
					return details, fmt.Errorf("malformed handshake ('a' digest)")
				}

				// handshaked
				return details, nil

sometime len(buffer) will more than 17
when like this, first receive 3 byte,then 45 byte,then 17 bytes, it work ok.
img_v2_044d6763-9d91-4ac6-ae8c-97e1e04083fg
but like this,first recevie 3 byte, then 45 byte, then 122 bytes, it not work.
img_v2_57cb63cb-3593-4908-87ff-4f388e172fbg
maybe handshake should think about TCP Stick Package.
maybe handshake should only need 17 bytes, the last bytes need take to distProto connection to read

Help create process ambiguity,or bug!

When supvisor strategy Type is gen SupervisorStrategySimpleOneForOne, use sup.StartChild API, created processes are shared a child object, Theoretically, a separate process requires a separate child object; Don't know that the design is intentional; I need a different process with a different object that can only be created using the node.spawn interface, not supvisor; When the type is SupervisorStrategySimpleOneForOne, whether to help special processing, separate process requires a separate object entities

Message dropped

Currently, it is found that multiple processes send a large number of messages to the same processing process, and the number of messages received by the processing process is discarded. Please kindly ask whether there is a problem with the capacity of mailbox, or we can learn RabbitMQ gen_server2 to handle it!

How to use ergo epmd instead of standard Erlang epmd?

Hi, first of all I would like to congratulate you for the excellent project.

I would like to know if and how it would be possible to run the epmd you provide instead of the standard Erlang epmd.
I would also like to know if there is any benchmark of you just doing the drop-in replacement of epmd using applications written purely in Erlang/Elixir.

Can you help me ?

Panic invalid memory address after reconnect a node

Describe the bug
Using the example genstage, change it to have two nodes, connect one to the other, disconnect, reconnect and panic

It kills the producer process directly, the entire node does NOT crash

  • Open two terminals
  • First one: go run ./producer
  • Second one: go run ./consumer
  • They should communicate and trigger events
  • CTRL + C on consumer
  • Restart it again
  • The error should show up
2023/01/06 17:06:25 WARNING! Server terminated <336B493D.0.1011>["producer"]. Panic reason: "invalid memory address or nil 

To Reproduce
Steps to reproduce the behavior:

I've made a gist with all the code and a readme there https://gist.github.com/davidroman0O/db04fa224c710a4ae33d53d28304e88b

Expected behavior
Maybe a better error why it has a nil pointer? why? where? stacktrace?

Or maybe it should just not panic

Environment (please complete the following information):

  • OS: Tested on Windows 10 and QubeOS 4.11
  • Framework Version v1.999.220
  • Number of CPU 12

Additional context

I was just experimenting with the examples, trying to plug a bit of my codebase of some side projects and end up with this issue
I came back on the examples to test it again with very little modification and the error appear!

How to Call a gen.ServerProcess?

Thank you for the great work you have done, I'm very excited about this library.

I think there is a problem with documentation. On pkg.go.dev there is an old version of documentation.

I browsed all the examples and I couldn't find information on how to make a Call request to a gen server process. What I wish is to get a process by name and then make a Call to it so that HandleCall will fire. In erlang it's easy and can be achieved with gen_server:call(pid,...) but in ergo it seams that I need to get hold of the gen.ServerProcess struct instance which appears to not be exposed.

Please help me with that.

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

How to wrap a blocking server in a GenServer?

In the Erlang/Elixir world, everybody models their processes the OTP way. But not in the Go world. A lot of them are modelled as goroutines. So here is an integration question between those two worlds.

I have a Kubernetes controller, implemented via operator-sdk which I want to wrap in an ergo process. My Kubernetes controller main has currently this as the last lines of code:

	setupLog.Info("starting manager")
	stopCh := ctrl.SetupSignalHandler()
	if err := mgr.Start(stopCh); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}

The Start function is blocking, unless if there is an error.

When I move it to a GenServer setup, I will add the mgr to my process state, and I can execute mgr.Start from a goroutine within the Init function. To get to know about a possible err result, I would pass an errCh to the goroutine.

Question remains how to integrate the lifecycle between my Genserver and mgr.

a. ergo node stops and calls Terminate on all processes: I close the stopCh (part of the state) and the controller stops as a result. I guess all fine here.
b. the mgr errors at start and this err is sent back via the errCh I mentioned. At what point should I check the errCh? To have a non-blocking check, I would at least use select on the errCh during Init, but due to timing, the message could come later. How can I "schedule" another check a bit later?

Using Struct Tags to Control Encoding

How can we convert go native types into etf.Term via reflection? Is a reflection api included?

E.g. slices of map[string]interface{} to etf.Term?

GitHub actions fail on all platforms due to integration E2E tests

Describe the bug

GitHub actions always fail by E2E tests on all platforms but without any details.

To Reproduce

Always fail on CI/CD - https://github.com/ergo-services/ergo/actions

Expected behavior

Should be success, not fail.

Screenshots

https://github.com/ergo-services/ergo/actions/runs/3271054409/jobs/5380380573#step:4:1242

...

=== Test TCP Server
Starting nodes: nodeTCP1@localhost: OK
...starting process (gen.TCP): OK
...makeing a new connection: OK
...send/recv data (10 bytes as 1 logic dataframe): OK
...send/recv data (7 bytes as a part of logic dataframe): OK
...send/recv data (5 bytes, must be 1 logic dataframe + extra 2 bytes): OK
...send/recv data (8 bytes, must be 1 logic dataframe): OK
...stopping process (gen.TCP): OK
--- PASS: TestTCP (0.00s)
=== RUN   TestUDP

=== Test UDP Server
Starting nodes: nodeUDP1@localhost: OK
...starting process (gen.UDP): OK
...send/receive data: OK
...stopping process (gen.UDP): OK
--- PASS: TestUDP (0.00s)
=== RUN   TestWeb

=== Test Web Server
Starting nodes: nodeWeb1@localhost: OK
...starting process (gen.Web): OK
...making simple GET request: OK
--- PASS: TestWeb (0.00s)
FAIL
FAIL	github.com/ergo-services/ergo/tests	61.923s
FAIL
Error: Process completed with exit code 1.

Environment (please complete the following information):

Fail on all platforms, below env for Ubuntu:

Operating System
  Ubuntu
  20.0.5
  LTS

go env
  GO111MODULE=""
  GOARCH="amd64"
  GOBIN=""
  GOCACHE="/home/runner/.cache/go-build"
  GOENV="/home/runner/.config/go/env"
  GOEXE=""
  GOEXPERIMENT=""
  GOFLAGS=""
  GOHOSTARCH="amd64"
  GOHOSTOS="linux"
  GOINSECURE=""
  GOMODCACHE="/home/runner/go/pkg/mod"
  GONOPROXY=""
  GONOSUMDB=""
  GOOS="linux"
  GOPATH="/home/runner/go"
  GOPRIVATE=""
  GOPROXY="https://proxy.golang.org,direct"
  GOROOT="/opt/hostedtoolcache/go/1.17.13/x64"
  GOSUMDB="sum.golang.org"
  GOTMPDIR=""
  GOTOOLDIR="/opt/hostedtoolcache/go/1.17.13/x64/pkg/tool/linux_amd64"
  GOVCS=""
  GOVERSION="go1.17.13"
  GCCGO="gccgo"
  AR="ar"
  CC="gcc"
  CXX="g++"
  CGO_ENABLED="1"
  GOMOD="/home/runner/work/ergo/ergo/go.mod"
  CGO_CFLAGS="-g -O2"
  CGO_CPPFLAGS=""
  CGO_CXXFLAGS="-g -O2"
  CGO_FFLAGS="-g -O2"
  CGO_LDFLAGS="-g -O2"
  PKG_CONFIG="pkg-config"
  GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1316604600=/tmp/go-build -gno-record-gcc-switches"

Additional context

I rerun it using gotestsum command and found out that each time the reason was different, for example, several variants:

https://github.com/dulanov/ergo/actions/runs/3315358118/jobs/5475849237#step:5:1

✖  tests (1m11.876s)

=== Failed
=== FAIL: tests TestNodeProxyConnect (3.29s)

=== Test Node Proxy
... connect NodeA to NodeC via NodeB: OK
... disconnect NodeC from NodeA: OK
... connect NodeA to NodeC via NodeB(transit proxy disabled): OK
... connect NodeA to NodeC (proxy feature support disabled) via NodeB: OK
... connect NodeA to NodeC (with wrong cookie) via NodeB: OK
... connect NodeA to NodeC (with correct cookie) via NodeB: OK
... connect NodeA to NodeD (with enabled encryption) via NodeB: OK
... start processA on NodeA: OK
... start processC on NodeC: OK
... start processD on NodeD: OK
... processA send short message to processC: OK
... processA send short message to processD (encrypted): OK
... processA send 10K message to processC (compressed): OK
... processA send 10K message to processD (compressed, encrypted): OK
... processA send 100K message to processC (fragmented):     server_test.go:897: result timeout

=== FAIL: tests TestServerMessageFlood (2.00s)

=== Test Server message flood 
Starting node: nodeGS1MessageFlood@localhost: OK
    wait for start of gs1source on "nodeGS1MessageFlood@localhost": OK
    wait for start of gs2source on "nodeGS1MessageFlood@localhost": OK
    wait for start of gs3source on "nodeGS1MessageFlood@localhost": OK
    wait for start of gs4source on "nodeGS1MessageFlood@localhost": OK
    wait for start of gs5source on "nodeGS1MessageFlood@localhost": OK
    wait for start of gsdest on "nodeGS1MessageFlood@localhost": OK
2022/10/24 18:57:19 WARNING! Server terminated <C58580E6.0.1014>["gs4source"]. Panic reason: "err on making a call: timed out" at github.com/ergo-services/ergo/tests.(*messageFloodSourceGS).HandleInfo[/home/runner/work/ergo/ergo/tests/server_test.go:675]
    server_test.go:897: result timeout

DONE 64 tests, 2 failures in 82.719s
Error: Process completed with exit code 1.

or https://github.com/dulanov/ergo/actions/runs/3315381867/jobs/5475904571#step:5:1

✖  tests (24.292s)

=== Failed
=== FAIL: tests TestNodeResolveExtra (unknown)

=== Test Node Resolve Extra 
... starting node1 with disabled TLS: OK
... starting node2 with enabled TLS: OK
... node1 resolves node2 with enabled TLS: OK
... node2 resolves node1 with disabled TLS: OK
... node1 connect to node2: OK
... disconnecting nodes: OK
... node2 connect to node1: --- FAIL: TestNodeResolveExtra (0.34s)
panic: runtime error: index out of range [0] with length 0 [recovered]
	panic: runtime error: index out of range [0] with length 0

goroutine 8317 [running]:
testing.tRunner.func1.2({0x14d8280, 0xc000152138})
	/Users/runner/hostedtoolcache/go/1.18.7/x64/src/testing/testing.go:1389 +0x24e
testing.tRunner.func1()
	/Users/runner/hostedtoolcache/go/1.18.7/x64/src/testing/testing.go:1392 +0x39f
panic({0x14d8280, 0xc000152138})
	/Users/runner/hostedtoolcache/go/1.18.7/x64/src/runtime/panic.go:838 +0x207
github.com/ergo-services/ergo/tests.TestNodeResolveExtra(0xc0010a3380)
	/Users/runner/work/ergo/ergo/tests/node_test.go:574 +0xefa
testing.tRunner(0xc0010a3380, 0x1547ba8)
	/Users/runner/hostedtoolcache/go/1.18.7/x64/src/testing/testing.go:1439 +0x102
created by testing.(*T).Run
	/Users/runner/hostedtoolcache/go/1.18.7/x64/src/testing/testing.go:1486 +0x35f

DONE 33 tests, 1 failure in 36.602s
Error: Process completed with exit code 1.

or https://github.com/dulanov/ergo/actions/runs/3315396040/jobs/5475936433#step:5:1

✖  tests (57.031s)

=== Failed
=== FAIL: tests TestNodeProxyConnect (3.33s)

=== Test Node Proxy
... connect NodeA to NodeC via NodeB: OK
... disconnect NodeC from NodeA: OK
... connect NodeA to NodeC via NodeB(transit proxy disabled): OK
... connect NodeA to NodeC (proxy feature support disabled) via NodeB: OK
... connect NodeA to NodeC (with wrong cookie) via NodeB: OK
... connect NodeA to NodeC (with correct cookie) via NodeB: OK
... connect NodeA to NodeD (with enabled encryption) via NodeB: OK
... start processA on NodeA: OK
... start processC on NodeC: OK
... start processD on NodeD: OK
... processA send short message to processC: OK
... processA send short message to processD (encrypted): OK
... processA send 10K message to processC (compressed): OK
... processA send 10K message to processD (compressed, encrypted): OK
... processA send 100K message to processC (fragmented):     server_test.go:897: result timeout

=== FAIL: tests TestRaftGet (0.09s)

=== Test GenRaft - get data
    started raft process <20790EB2.0.1011> with serial 8
    started raft process <B7995A78.0.1011> with serial 5
    started raft process <A4BCA225.0.1011> with serial 6
    started raft process <335CF6EF.0.1011> with serial 3
    started raft process <82F7529F.0.1011> with serial 1
    started raft process <15170655.0.1011> with serial 2
    raft process <20790EB2.0.1011> has already latest serial. skip it
    get serials (6...8) on <B7995A78.0.1011> to reach the leader's serial:     raft_data_test.go:124: no peers with requested serial

=== FAIL: tests TestRaftLeader (0.21s)

=== Test GenRaft - build quorum, leader election
    cluster with  2 distributed raft processes. no quorum, no leader: OK
    cluster with  3 distributed raft processes. quorum of  3 members with 1 leader: OK
    cluster with  4 distributed raft processes. quorum of  3 members with 1 leader + 1 follower(s):     raft_test.go:246: wrong leader serial

DONE 64 tests, 3 failures in 66.270s
Error: Process completed with exit code 1.

Failed to connect from elixir 1.7.4, otp 21

iex(c@alex-pc)1> :net_adm.ping(:'b@alex-pc')    

03:23:46.922 [error] ** :"c@alex-pc": Connection attempt to node :"b@alex-pc" aborted since it cannot handle ['NEW_FUN_TAGS'].**

:pang

I doubt benchmarks be fair !!!

https://erlangforums.com/t/ring-benchmark-erlang-vs-go/684/12

in erlang community we did some benchkmarks around (raw golang) and ( erlang )
erlang was winner in message passing.
erlang internally used lock-free algorithm for registry and communication between mailbox.
but golang used mutex for channels !!! and in your framework heavily using mutex for synchronization !!!

how is ergo with overhead of framework can beats erlang ??!!!

Remove use of `syscall` package

When trying to integrate ergo in my application, I bumped into the following compilation problem on my Mac:

# github.com/halturin/ergo
../../golang/pkg/mod/github.com/halturin/ergo@v1.1.0/node.go:759:63: undefined: syscall.TCP_KEEPINTVL
make: *** [build-controller] Error 2

When checking the docs, I noticed that this constant is indeed only available for GOOS=linux and not GOOS=darwin:

Nevertheless, this package contains a deprecation message:

Deprecated: this package is locked down. Callers should use the corresponding package in the golang.org/x/sys repository instead. That is also where updates required by new systems or versions should be applied. See https://golang.org/s/go1.4-syscall for more information.

So I guess the use of syscall should be removed and replaced by package golang.org/x/sys. For the constant at hand, there is the constant SO_KEEPALIVE in both the unix and windows packages:

https://pkg.go.dev/golang.org/x/[email protected]/unix?tab=doc#pkg-constants
https://pkg.go.dev/golang.org/x/[email protected]/windows?tab=doc#pkg-constants

Could you update ergo with this change and make a new release?

Platform specific build tags is probably what you need here:
https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool

Issues while running the README example

Hi,

I tried to run the README example, but I had some issues.

First, the error was:

# github.com/halturin/ergo
vendor/github.com/halturin/ergo/node.go:759:63: undefined: syscall.TCP_KEEPINTVL

Then, I just changed the syscall.TCP_KEEPINTVL to syscall.TCP_KEEPALIVE, and the new error was:

Warning: recovered process: {node@localhost 1003 1 1} &runtime.TypeAssertionError{_interface:(*runtime._type)(nil), concrete:(*runtime._type)(0x11853a0), asserted:(*runtime._type)(0x117a240), missingMethod:"Init"}
exited

I noticed that the last release was a few months ago and there was a few changes on branch master since that. Then, I just cloned the repo, but I keep having the following errors:

Warning: recovered process(name: gs1){node@localhost 1005 1 1} &runtime.TypeAssertionError{_interface:(*runtime._type)(nil), concrete:(*runtime._type)(0x1287620), asserted:(*runtime._type)(0x12786c0), missingMethod:"Init"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x1241522]

goroutine 1 [running]:
github.com/halturin/ergo.(*Process).Self(...)
	.../github.com/Ocelani/playground/vendor/github.com/halturin/ergo/process.go:92
main.main()
	.../github.com/Ocelani/playground/main.go:68 +0x182
exit status 2

Without the github.com/Ocelani/playground/main.go:68 source line

Warning: recovered process(name: gs1){node@localhost 1003 1 1} &runtime.TypeAssertionError{_interface:(*runtime._type)(nil), concrete:(*runtime._type)(0x12874c0), asserted:(*runtime._type)(0x1278560), missingMethod:"Init"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x68 pc=0x124151a]

goroutine 1 [running]:
main.main()
	.../github.com/Ocelani/playground/main.go:71 +0x17a
exit status 2

I could send a pull request solution, but I didn't find any yet, just arrived here.
Although, maybe I am missing something, any pre requisite or something like that.

If there is any solution, could you help me?

Thanks!

Any example show how to send a message to erlang node?

erlang code:

-module(kvs).                                 
-export([start/0,store/2,lookup/1]).          
start()->                                     
    register(kvs,spawn(fun() -> loop() end)). 
                                              
store(Key,Value)->                            
    io:format("xxx\n"),                       
    rpc({store,Key,Value}).                   
                                              
lookup(Key)->                                 
    rpc({lookup,Key}).                        
                                              
rpc(Q)->                                      
    kvs ! {self(), Q},                        
    receive                                   
        {kvs, Reply} ->                       
            Reply                             
    end.                                      
                                              
loop() ->                                     
    receive                                   
        {From, {store, Key, Value}} ->        
            put(Key, {ok, Value}),            
            From ! {kvs, true},               
            loop();                           
        {From, {lookup, Key}} ->              
            From ! {kvs, get(Key)},           
            loop()                            
    end.                                      

I use simple/GenServer.go

process.Call(etf.Tuple{"kvs", "erlmain@localhost"}, etf.Term(etf.List{"weather", "fine"}))

can not recv the result from erlang node erlmain@localhost

resource is taken

Call message from remote node, error code is received for the first time: Can't connect to [email protected] Resource is taken, continue communication with no problem, not sure whether the initial connection of the node is wrong, or the process connecting to the remote node is wrong? Do you need to ping each other in advance to avoid this problem?

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

Command timed out when CallRPC is used

Hello.

I've installed erlang@22 via homebrew and started an erlang node:

➜ ~ /usr/local/opt/erlang@22/bin/erl -sname demo@MBP-Alexandr -setcookie 123 -kernel logger_level info
Erlang/OTP 22 [erts-10.7.2.8] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

=PROGRESS REPORT==== 28-Apr-2021::18:57:43.349637 ===
    application: kernel
    started_at: 'demo@MBP-Alexandr'
=PROGRESS REPORT==== 28-Apr-2021::18:57:43.354807 ===
    application: stdlib
    started_at: 'demo@MBP-Alexandr'
Eshell V10.7.2.8  (abort with ^G)
(demo@MBP-Alexandr)1>

And then I use the following code to send a command via RPC:

		log.Println("Attempt")
		q, e := demoGS.process.CallRPCWithTimeout(30, "demo@MBP-Alexandr", "erlang", "node")
		log.Println(q, e)
		process.Kill()

But I always get an error: timeout

So logs from demo erlang node have the following text:

(demo@MBP-Alexandr)1> =ERROR REPORT==== 28-Apr-2021::18:59:43.240434 ===
** Node 'test@MBP-Alexandr' not responding **
** Removing (timedout) connection **

And logs from go-erlang:

2021/04/28 18:58:03 [test@MBP-Alexandr] RPC calling: demo@MBP-Alexandr:erlang:node
2021/04/28 18:58:03 [test@MBP-Alexandr] sending message by tuple [rex demo@MBP-Alexandr]
2021/04/28 18:58:03 [test@MBP-Alexandr] registering peer &{demo@MBP-Alexandr [<nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil> <nil>] 0 12 {0 0}}
2021/04/28 18:58:03 Node control: etf.Tuple{6, etf.Pid{Node:"demo@MBP-Alexandr", ID:0x36, Serial:0x0, Creation:0x2}, "", "global_name_server"}
2021/04/28 18:58:03 [test@MBP-Alexandr] sending message by name global_name_server
2021/04/28 18:58:03 [test@MBP-Alexandr] sending message by pid {test@MBP-Alexandr 1003 1 1}
2021/04/28 18:58:03 [test@MBP-Alexandr]. {test@MBP-Alexandr 1003 1 1} got message from etf.Pid{Node:"demo@MBP-Alexandr", ID:0x36, Serial:0x0, Creation:0x2}
2021/04/28 18:58:03 GLOBAL_NAME_SERVER: HandleCast: etf.Tuple{"init_connect", etf.Tuple{5, -576460752303423488}, "demo@MBP-Alexandr", etf.Tuple{"locker", "no_longer_a_pid", etf.List{}, etf.Pid{Node:"demo@MBP-Alexandr", ID:0x37, Serial:0x0, Creation:0x2}}}

Why does it happen? Can I use RPC calls?

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.